summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue28
-rw-r--r--app/assets/javascripts/diffs/store/getters.js19
-rw-r--r--app/assets/javascripts/diffs/store/utils.js1
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js24
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js7
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js34
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js23
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue12
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue5
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue7
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue70
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue44
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row_header.vue25
-rw-r--r--app/assets/stylesheets/framework/modal.scss38
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/profile.scss10
-rw-r--r--app/controllers/concerns/uploads_actions.rb6
-rw-r--r--app/controllers/projects/issues_controller.rb18
-rw-r--r--app/controllers/projects/settings/operations_controller.rb9
-rw-r--r--app/helpers/projects_helper.rb3
-rw-r--r--app/mailers/emails/issues.rb11
-rw-r--r--app/mailers/previews/notify_preview.rb4
-rw-r--r--app/models/commit_collection.rb6
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb14
-rw-r--r--app/models/merge_request.rb24
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb29
-rw-r--r--app/models/repository.rb12
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/policies/project_snippet_policy.rb11
-rw-r--r--app/presenters/merge_request_presenter.rb4
-rw-r--r--app/serializers/merge_request_widget_entity.rb7
-rw-r--r--app/services/issues/import_csv_service.rb53
-rw-r--r--app/services/notes/quick_actions_service.rb2
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb14
-rw-r--r--app/services/projects/operations/update_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb41
-rw-r--r--app/services/upload_service.rb2
-rw-r--r--app/validators/url_validator.rb4
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml2
-rw-r--r--app/views/notify/import_issues_csv_email.html.haml18
-rw-r--r--app/views/notify/import_issues_csv_email.text.erb11
-rw-r--r--app/views/projects/_issuable_by_email.html.haml2
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/issues/_import_export.svg1
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml41
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml9
-rw-r--r--app/views/projects/issues/import_csv/_modal.html.haml24
-rw-r--r--app/views/projects/issues/index.html.haml5
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml30
-rw-r--r--app/views/projects/settings/operations/show.html.haml1
-rw-r--r--app/views/shared/empty_states/_issues.html.haml13
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml2
-rw-r--r--app/views/shared/notes/_comment_button.html.haml19
-rw-r--r--app/views/shared/notes/_edit.html.haml2
-rw-r--r--app/views/shared/notes/_edit_form.html.haml8
-rw-r--r--app/views/shared/notes/_form.html.haml6
-rw-r--r--app/views/shared/notes/_hints.html.haml12
-rw-r--r--app/views/shared/notes/_note.html.haml4
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml14
-rw-r--r--app/views/users/_overview.html.haml30
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/import_issues_csv_worker.rb20
-rwxr-xr-xbin/secpick190
-rw-r--r--changelogs/unreleased/49231-import-issues-csv.yml5
-rw-r--r--changelogs/unreleased/53966-hashed-storage-read-only.yml5
-rw-r--r--changelogs/unreleased/55192-about-link-in-new-window.yml5
-rw-r--r--changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml5
-rw-r--r--changelogs/unreleased/56019-archived-stuck.yml5
-rw-r--r--changelogs/unreleased/56076-releases-margin.yml5
-rw-r--r--changelogs/unreleased/custom-helm-chart-repo.yml5
-rw-r--r--changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-shared-notes.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml5
-rw-r--r--changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml5
-rw-r--r--changelogs/unreleased/update-smooshpack.yml5
-rw-r--r--config/routes/project.rb1
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--danger/commit_messages/Dangerfile2
-rw-r--r--db/fixtures/development/04_project.rb200
-rw-r--r--db/migrate/20181212171634_create_error_tracking_settings.rb17
-rw-r--r--db/schema.rb8
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md2
-rw-r--r--doc/administration/high_availability/redis.md8
-rw-r--r--doc/administration/high_availability/redis_source.md8
-rw-r--r--doc/administration/housekeeping.md19
-rw-r--r--doc/administration/incoming_email.md70
-rw-r--r--doc/administration/integration/terminal.md39
-rw-r--r--doc/administration/raketasks/check.md6
-rw-r--r--doc/administration/repository_storage_types.md4
-rw-r--r--doc/administration/troubleshooting/debug.md4
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/lint.md7
-rw-r--r--doc/api/oauth2.md48
-rw-r--r--doc/api/project_clusters.md346
-rw-r--r--doc/api/projects.md13
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/ci/environments.md2
-rw-r--r--doc/ci/interactive_web_terminal/index.md5
-rw-r--r--doc/ci/pipelines.md6
-rw-r--r--doc/ci/variables/README.md17
-rw-r--r--doc/ci/yaml/README.md6
-rw-r--r--doc/development/adding_database_indexes.md4
-rw-r--r--doc/development/background_migrations.md6
-rw-r--r--doc/development/contributing/index.md34
-rw-r--r--doc/development/documentation/styleguide.md2
-rw-r--r--doc/development/emails.md30
-rw-r--r--doc/development/fe_guide/components.md7
-rw-r--r--doc/development/fe_guide/droplab/droplab.md36
-rw-r--r--doc/development/fe_guide/droplab/plugins/ajax.md8
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md4
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md6
-rw-r--r--doc/development/fe_guide/index.md8
-rw-r--r--doc/development/fe_guide/performance.md4
-rw-r--r--doc/development/file_storage.md16
-rw-r--r--doc/development/github_importer.md22
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/instrumentation.md10
-rw-r--r--doc/development/merge_request_performance_guidelines.md4
-rw-r--r--doc/development/migration_style_guide.md10
-rw-r--r--doc/development/new_fe_guide/development/components.md14
-rw-r--r--doc/development/new_fe_guide/development/performance.md8
-rw-r--r--doc/development/new_fe_guide/development/testing.md20
-rw-r--r--doc/development/new_fe_guide/modules/index.md4
-rw-r--r--doc/development/performance.md26
-rw-r--r--doc/development/policies.md22
-rw-r--r--doc/development/polymorphic_associations.md12
-rw-r--r--doc/development/post_deployment_migrations.md6
-rw-r--r--doc/development/rake_tasks.md8
-rw-r--r--doc/development/reusing_abstractions.md18
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md8
-rw-r--r--doc/development/testing_guide/frontend_testing.md20
-rw-r--r--doc/development/understanding_explain_plans.md10
-rw-r--r--doc/development/utilities.md14
-rw-r--r--doc/development/verifying_database_capabilities.md6
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md2
-rw-r--r--doc/install/structure.md8
-rw-r--r--doc/integration/gmail_action_buttons_for_gitlab.md8
-rw-r--r--doc/integration/omniauth.md4
-rw-r--r--doc/policy/maintenance.md20
-rw-r--r--doc/security/crime_vulnerability.md10
-rw-r--r--doc/topics/autodevops/index.md65
-rw-r--r--doc/university/glossary/README.md12
-rw-r--r--doc/university/high-availability/aws/README.md26
-rw-r--r--doc/university/training/end-user/README.md17
-rw-r--r--doc/university/training/topics/getting_started.md14
-rw-r--r--doc/university/training/topics/git_add.md10
-rw-r--r--doc/university/training/topics/git_log.md10
-rw-r--r--doc/university/training/topics/merge_conflicts.md9
-rw-r--r--doc/university/training/topics/rollback_commits.md14
-rw-r--r--doc/university/training/topics/stash.md15
-rw-r--r--doc/university/training/topics/subtree.md18
-rw-r--r--doc/university/training/topics/unstage.md8
-rw-r--r--doc/update/6.9-to-7.0.md4
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md16
-rw-r--r--doc/update/7.1-to-7.2.md4
-rw-r--r--doc/update/7.2-to-7.3.md4
-rw-r--r--doc/update/7.3-to-7.4.md6
-rw-r--r--doc/update/7.4-to-7.5.md4
-rw-r--r--doc/update/7.5-to-7.6.md4
-rw-r--r--doc/update/7.6-to-7.7.md4
-rw-r--r--doc/update/7.7-to-7.8.md6
-rw-r--r--doc/update/7.8-to-7.9.md6
-rw-r--r--doc/update/7.9-to-7.10.md6
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md8
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md6
-rw-r--r--doc/user/gitlab_com/index.md10
-rw-r--r--doc/user/group/subgroups/index.md4
-rw-r--r--doc/user/instance_statistics/user_cohorts.md4
-rw-r--r--doc/user/profile/account/two_factor_authentication.md12
-rw-r--r--doc/user/project/container_registry.md6
-rw-r--r--doc/user/project/img/issue_boards_multiple.pngbin6086 -> 22623 bytes
-rw-r--r--doc/user/project/import/github.md24
-rw-r--r--doc/user/project/integrations/emails_on_push.md8
-rw-r--r--doc/user/project/integrations/jira.md24
-rw-r--r--doc/user/project/integrations/prometheus.md15
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md18
-rw-r--r--doc/user/project/issue_board.md1
-rw-r--r--doc/user/project/issues/create_new_issue.md40
-rw-r--r--doc/user/project/issues/csv_import.md45
-rw-r--r--doc/user/project/issues/img/import_csv_button.pngbin0 -> 4342 bytes
-rw-r--r--doc/user/project/issues/index.md9
-rw-r--r--doc/user/project/merge_requests/img/create_from_email.pngbin55777 -> 112256 bytes
-rw-r--r--doc/user/project/merge_requests/index.md16
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md10
-rw-r--r--doc/user/project/pipelines/settings.md8
-rw-r--r--doc/user/search/index.md12
-rw-r--r--doc/workflow/lfs/lfs_administration.md22
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md26
-rw-r--r--doc/workflow/releases.md1
-rw-r--r--doc/workflow/time_tracking.md16
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/gitlab/background_migration.rb13
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/diff/lines_unfolder.rb11
-rw-r--r--lib/gitlab/email/attachment_uploader.rb4
-rw-r--r--lib/gitlab/etag_caching/middleware.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml4
-rw-r--r--lib/gitlab/import_export/relation_factory.rb1
-rw-r--r--lib/gitlab/import_export/uploads_manager.rb2
-rw-r--r--lib/gitlab/middleware/basic_health_check.rb2
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb2
-rw-r--r--lib/gitlab/repository_cache.rb4
-rw-r--r--lib/gitlab/request_context.rb2
-rw-r--r--lib/gitlab/seeder.rb13
-rw-r--r--lib/gitlab/url_blocker.rb18
-rw-r--r--locale/gitlab.pot134
-rw-r--r--package.json4
-rw-r--r--qa/README.md23
-rw-r--r--qa/qa/page/base.rb8
-rw-r--r--qa/qa/page/component/select2.rb6
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/web_ide/edit.rb22
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb50
-rw-r--r--qa/qa/support/page/logging.rb16
-rw-r--r--qa/spec/page/logging_spec.rb14
-rw-r--r--qa/spec/spec_helper.rb34
-rw-r--r--qa/spec/spec_helper_spec.rb264
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb66
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb168
-rw-r--r--spec/db/schema_spec.rb7
-rw-r--r--spec/factories/project_error_tracking_settings.rb10
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb4
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb46
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb39
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json4
-rw-r--r--spec/fixtures/csv_comma.csv4
-rw-r--r--spec/fixtures/csv_semicolon.csv5
-rw-r--r--spec/fixtures/csv_tab.csv4
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json94
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap37
-rw-r--r--spec/frontend/vue_shared/components/file_row_header_spec.js36
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js5
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js17
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js4
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js36
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js25
-rw-r--r--spec/javascripts/lib/utils/datetime_utility_spec.js40
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js16
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js12
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js131
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js43
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb44
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb115
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb32
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml5
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb22
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/mailers/emails/issues_spec.rb33
-rw-r--r--spec/models/commit_collection_spec.rb25
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb36
-rw-r--r--spec/models/merge_request_spec.rb28
-rw-r--r--spec/models/note_spec.rb15
-rw-r--r--spec/models/project_spec.rb46
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/git_http_spec.rb4
-rw-r--r--spec/services/issues/import_csv_service_spec.rb64
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb14
-rw-r--r--spec/services/projects/operations/update_service_spec.rb61
-rw-r--r--spec/services/upload_service_spec.rb4
-rw-r--r--spec/validators/url_validator_spec.rb51
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb39
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb39
-rw-r--r--spec/workers/import_issues_csv_worker_spec.rb21
-rw-r--r--yarn.lock16
286 files changed, 4248 insertions, 1162 deletions
diff --git a/Gemfile b/Gemfile
index 8d5fd8bbbcb..1df4584afb7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,7 +16,7 @@ gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
# Supported DBs
gem 'mysql2', '~> 0.4.10', group: :mysql
-gem 'pg', '~> 0.18.2', group: :postgres
+gem 'pg', '~> 1.1', group: :postgres
gem 'rugged', '~> 0.27'
gem 'grape-path-helpers', '~> 1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4dda0bcad18..d708d0e9740 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -575,7 +575,7 @@ GEM
atomic (>= 1.0.0)
peek
redis
- pg (0.18.4)
+ pg (1.1.3)
po_to_json (1.0.1)
json (>= 1.6.0)
powerpack (0.1.1)
@@ -1084,7 +1084,7 @@ DEPENDENCIES
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
- pg (~> 0.18.2)
+ pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.5.1)
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index f40a7b25fde..eb8f274aff3 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -34,14 +34,18 @@ export default {
if (search === '') return this.renderTreeList ? this.tree : this.allBlobs;
- return this.allBlobs.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
- },
- rowDisplayTextKey() {
- if (this.renderTreeList && this.search.trim() === '') {
- return 'name';
- }
+ return this.allBlobs.reduce((acc, folder) => {
+ const tree = folder.tree.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
- return 'path';
+ if (tree.length) {
+ return acc.concat({
+ ...folder,
+ tree,
+ });
+ }
+
+ return acc;
+ }, []);
},
},
methods: {
@@ -119,7 +123,7 @@ export default {
</button>
</div>
</div>
- <div class="tree-list-scroll">
+ <div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
<template v-if="filteredTreeList.length">
<file-row
v-for="file in filteredTreeList"
@@ -129,8 +133,6 @@ export default {
:hide-extra-on-tree="true"
:extra-component="$options.FileRowStats"
:show-changed-icon="true"
- :display-text-key="rowDisplayTextKey"
- :should-truncate-start="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
@@ -148,3 +150,9 @@ export default {
</div>
</div>
</template>
+
+<style>
+.tree-list-blobs .file-row-name {
+ margin-left: 12px;
+}
+</style>
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index fdf1efbb10e..86c0c7190f9 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -74,7 +74,24 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.file_hash === fileHash);
-export const allBlobs = state => Object.values(state.treeEntries).filter(f => f.type === 'blob');
+export const allBlobs = state =>
+ Object.values(state.treeEntries)
+ .filter(f => f.type === 'blob')
+ .reduce((acc, file) => {
+ const { parentPath } = file;
+
+ if (parentPath && !acc.some(f => f.path === parentPath)) {
+ acc.push({
+ path: parentPath,
+ isHeader: true,
+ tree: [],
+ });
+ }
+
+ acc.find(f => f.path === parentPath).tree.push(file);
+
+ return acc;
+ }, []);
export const diffFilesLength = state => state.diffFiles.length;
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 2fe20551642..f427367c11e 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -318,6 +318,7 @@ export const generateTreeList = files =>
fileHash: file.file_hash,
addedLines: file.added_lines,
removedLines: file.removed_lines,
+ parentPath: parent ? `${parent.path}/` : '/',
});
} else {
Object.assign(entry, {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 8178821be3d..570d3b712e0 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -221,13 +221,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
- // eslint-disable-next-line no-template-curly-in-string
- insertTpl: '${atwho-at}${id}',
+ insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
+ skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(issues) {
@@ -238,6 +238,7 @@ class GfmAutoComplete {
return {
id: i.iid,
title: sanitize(i.title),
+ reference: i.reference,
search: `${i.iid} ${i.title}`,
};
});
@@ -287,13 +288,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
- // eslint-disable-next-line no-template-curly-in-string
- insertTpl: '${atwho-at}${id}',
+ insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
+ skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(merges) {
@@ -304,6 +305,7 @@ class GfmAutoComplete {
return {
id: m.iid,
title: sanitize(m.title),
+ reference: m.reference,
search: `${m.iid} ${m.title}`,
};
});
@@ -397,7 +399,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
@@ -596,8 +598,12 @@ GfmAutoComplete.Labels = {
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
- templateFunction(id, title) {
- return `<li><small>${id}</small> ${_.escape(title)}</li>`;
+ insertTemplateFunction(value) {
+ // eslint-disable-next-line no-template-curly-in-string
+ return value.reference || '${atwho-at}${id}';
+ },
+ templateFunction({ id, title, reference }) {
+ return `<li><small>${reference || id}</small> ${_.escape(title)}</li>`;
},
};
// Milestones
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 786ab16992d..d2b7ce18290 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -240,14 +240,19 @@ export default {
<div
v-if="job.archived"
ref="sticky"
- class="js-archived-job prepend-top-default archived-sticky sticky-top"
+ class="js-archived-job prepend-top-default archived-job"
+ :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">
+ <div
+ v-if="hasTrace"
+ class="build-trace-container"
+ :class="{ 'prepend-top-default': !job.archived }"
+ >
<log-top-bar
:class="{
'sidebar-expanded': isSidebarOpen,
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 9e22cdc04e9..fc34d243dd7 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -118,12 +118,13 @@ export const handleLocationHash = () => {
// Check if element scrolled into viewport from above or below
// Courtesy http://stackoverflow.com/a/7557433/414749
-export const isInViewport = el => {
+export const isInViewport = (el, offset = {}) => {
const rect = el.getBoundingClientRect();
+ const { top, left } = offset;
return (
- rect.top >= 0 &&
- rect.left >= 0 &&
+ rect.top >= (top || 0) &&
+ rect.left >= (left || 0) &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 59007d5950e..01dbbb9dd16 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -8,6 +8,14 @@ import { languageCode, s__ } from '../../locale';
window.timeago = timeago;
/**
+ * This method allows you to create new Date instance from existing
+ * date instance without keeping the reference.
+ *
+ * @param {Date} date
+ */
+export const newDate = date => (date instanceof Date ? new Date(date.getTime()) : new Date());
+
+/**
* Returns i18n month names array.
* If `abbreviated` is provided, returns abbreviated
* name.
@@ -321,23 +329,35 @@ export const getSundays = date => {
/**
* Returns list of Dates representing a timeframe of months from startDate and length
+ * This method also supports going back in time when `length` is negative number
*
- * @param {Date} startDate
+ * @param {Date} initialStartDate
* @param {Number} length
*/
-export const getTimeframeWindowFrom = (startDate, length) => {
- if (!(startDate instanceof Date) || !length) {
+export const getTimeframeWindowFrom = (initialStartDate, length) => {
+ if (!(initialStartDate instanceof Date) || !length) {
return [];
}
+ const startDate = newDate(initialStartDate);
+ const moveMonthBy = length > 0 ? 1 : -1;
+
+ startDate.setDate(1);
+ startDate.setHours(0, 0, 0, 0);
+
// Iterate and set date for the size of length
// and push date reference to timeframe list
- const timeframe = new Array(length)
- .fill()
- .map((val, i) => new Date(startDate.getFullYear(), startDate.getMonth() + i, 1));
+ const timeframe = new Array(Math.abs(length)).fill().map(() => {
+ const currentMonth = startDate.getTime();
+ startDate.setMonth(startDate.getMonth() + moveMonthBy);
+ return new Date(currentMonth);
+ });
// Change date of last timeframe item to last date of the month
- timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
+ // when length is positive
+ if (length > 0) {
+ timeframe[timeframe.length - 1].setDate(totalDaysInMonth(timeframe[timeframe.length - 1]));
+ }
return timeframe;
};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 7cc7cd6d20e..c49b1bb5a2f 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -72,6 +72,29 @@ export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3
*/
export const truncateSha = sha => sha.substr(0, 8);
+const ELLIPSIS_CHAR = '…';
+export const truncatePathMiddleToLength = (text, maxWidth) => {
+ let returnText = text;
+ let ellipsisCount = 0;
+
+ while (returnText.length >= maxWidth) {
+ const textSplit = returnText.split('/').filter(s => s !== ELLIPSIS_CHAR);
+ const middleIndex = Math.floor(textSplit.length / 2);
+
+ returnText = textSplit
+ .slice(0, middleIndex)
+ .concat(
+ new Array(ellipsisCount + 1).fill().map(() => ELLIPSIS_CHAR),
+ textSplit.slice(middleIndex + 1),
+ )
+ .join('/');
+
+ ellipsisCount += 1;
+ }
+
+ return returnText;
+};
+
/**
* Capitalizes first character
*
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 12224e36ba2..e2cffe0b4b4 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -6,6 +6,7 @@ export default {
components: {
GlAreaChart,
},
+ inheritAttrs: false,
props: {
graphData: {
type: Object,
@@ -25,6 +26,11 @@ export default {
);
},
},
+ alertData: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
computed: {
chartData() {
@@ -74,9 +80,6 @@ export default {
const [date, value] = params;
return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)];
},
- onCreated(chart) {
- this.$emit('created', chart);
- },
},
};
</script>
@@ -88,10 +91,11 @@ export default {
<div class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
+ v-bind="$attrs"
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
- @created="onCreated"
+ :thresholds="alertData"
/>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2d9c5050c9b..cea5c1a56ca 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -144,6 +144,9 @@ export default {
}
},
methods: {
+ getGraphAlerts(graphId) {
+ return this.alertData ? this.alertData[graphId] || {} : {};
+ },
getGraphsData() {
this.state = 'loading';
Promise.all([
@@ -223,6 +226,8 @@ export default {
:tags-path="tagsPath"
:show-legend="showLegend"
:small-graph="forceSmallGraph"
+ :alert-data="getGraphAlerts(graphData.id)"
+ group-id="monitor-area-chart"
>
<!-- EE content -->
{{ null }}
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index e78596f8b52..db62ddb3ecd 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -149,6 +149,9 @@ export default {
return shouldResolve || shouldToggleState;
},
+ handleKeySubmit() {
+ this.handleUpdate();
+ },
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
@@ -216,8 +219,8 @@ export default {
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.meta.enter="handleUpdate();"
- @keydown.ctrl.enter="handleUpdate();"
+ @keydown.meta.enter="handleKeySubmit();"
+ @keydown.ctrl.enter="handleKeySubmit();"
@keydown.up="editMyLastNote();"
@keydown.esc="cancelHandler(true);"
></textarea>
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 4295fef8f0a..7ed1b407ddd 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -87,7 +87,7 @@ export default {
<div
v-if="assets.links.length || assets.sources.length"
- Sclass="card-text prepend-top-default"
+ class="card-text prepend-top-default"
>
<b>
{{ __('Assets') }}
@@ -98,7 +98,7 @@ export default {
<li v-for="link in assets.links" :key="link.name" class="append-bottom-8">
<gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url">
<icon name="package" class="align-middle append-right-4 align-text-bottom" />
- {{ link.name }}
+ {{ link.name }} <span v-if="link.external"> {{ __('(external source)') }}</span>
</gl-link>
</li>
</ul>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 27352e0b2b1..f6f445c1cef 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,10 +1,14 @@
<script>
-import statusIcon from '../mr_widget_status_icon.vue';
+import $ from 'jquery';
+import _ from 'underscore';
+import { s__, sprintf } from '~/locale';
+import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
+import StatusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetConflicts',
components: {
- statusIcon,
+ StatusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
@@ -15,6 +19,52 @@ export default {
default: () => ({}),
},
},
+ computed: {
+ popoverTitle() {
+ return s__(
+ 'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
+ );
+ },
+ showResolveButton() {
+ return this.mr.conflictResolutionPath && this.mr.canMerge;
+ },
+ showPopover() {
+ return this.showResolveButton && this.mr.sourceBranchProtected;
+ },
+ },
+ mounted() {
+ if (this.showPopover) {
+ const $el = $(this.$refs.popover);
+
+ $el
+ .popover({
+ html: true,
+ trigger: 'focus',
+ container: 'body',
+ placement: 'top',
+ template:
+ '<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>',
+ title: s__(
+ 'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
+ ),
+ content: sprintf(
+ s__('mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}'),
+ {
+ link_start: `<a href="${_.escape(
+ this.mr.conflictsDocsPath,
+ )}" target="_blank" rel="noopener noreferrer">`,
+ link_end: '</a>',
+ },
+ false,
+ ),
+ })
+ .on('mouseenter', mouseenter)
+ .on('mouseleave', debouncedMouseleave(300))
+ .on('show.bs.popover', () => {
+ window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
+ });
+ }
+ },
};
</script>
<template>
@@ -38,13 +88,15 @@ To merge this request, first rebase locally.`)
}}
</span>
</span>
- <a
- v-if="mr.canMerge && mr.conflictResolutionPath"
- :href="mr.conflictResolutionPath"
- class="js-resolve-conflicts-button btn btn-default btn-sm"
- >
- {{ s__('mrWidget|Resolve conflicts') }}
- </a>
+ <span v-if="showResolveButton" ref="popover">
+ <a
+ :href="mr.conflictResolutionPath"
+ :disabled="mr.sourceBranchProtected"
+ class="js-resolve-conflicts-button btn btn-default btn-sm"
+ >
+ {{ s__('mrWidget|Resolve conflicts') }}
+ </a>
+ </span>
<button
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-sm"
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 c777bcca0fa..e5a52c6a7f6 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
@@ -29,6 +29,8 @@ export default class MergeRequestStore {
this.title = data.title;
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
+ this.sourceBranchProtected = data.source_branch_protected;
+ this.conflictsDocsPath = data.conflicts_docs_path;
this.mergeStatus = data.merge_status;
this.commitMessage = data.merge_commit_message;
this.shortMergeCommitSha = data.short_merge_commit_sha;
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 9e713673678..4c884c55a30 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -1,11 +1,13 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import FileHeader from '~/vue_shared/components/file_row_header.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
export default {
name: 'FileRow',
components: {
+ FileHeader,
FileIcon,
Icon,
ChangedFileIcon,
@@ -34,21 +36,10 @@ export default {
required: false,
default: false,
},
- displayTextKey: {
- type: String,
- required: false,
- default: 'name',
- },
- shouldTruncateStart: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
mouseOver: false,
- truncateStart: 0,
};
},
computed: {
@@ -60,7 +51,7 @@ export default {
},
levelIndentation() {
return {
- marginLeft: `${this.level * 16}px`,
+ marginLeft: this.level ? `${this.level * 16}px` : null,
};
},
fileClass() {
@@ -71,14 +62,8 @@ export default {
'is-open': this.file.opened,
};
},
- outputText() {
- const text = this.file[this.displayTextKey];
-
- if (this.truncateStart === 0) {
- return text;
- }
-
- return `...${text.substring(this.truncateStart, text.length)}`;
+ childFilesLevel() {
+ return this.file.isHeader ? 0 : this.level + 1;
},
},
watch: {
@@ -92,15 +77,6 @@ export default {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
-
- if (this.shouldTruncateStart) {
- const { scrollWidth, offsetWidth } = this.$refs.textOutput;
- const textOverflow = scrollWidth - offsetWidth;
-
- if (textOverflow > 0) {
- this.truncateStart = Math.ceil(textOverflow / 5) + 3;
- }
- }
},
methods: {
toggleTreeOpen(path) {
@@ -156,7 +132,9 @@ export default {
<template>
<div>
+ <file-header v-if="file.isHeader" :path="file.path" />
<div
+ v-else
:class="fileClass"
class="file-row"
role="button"
@@ -175,7 +153,7 @@ export default {
:size="16"
/>
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
- {{ outputText }}
+ {{ file.name }}
</span>
<component
:is="extraComponent"
@@ -185,17 +163,15 @@ export default {
/>
</div>
</div>
- <template v-if="file.opened">
+ <template v-if="file.opened || file.isHeader">
<file-row
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
- :level="level + 1"
+ :level="childFilesLevel"
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
- :display-text-key="displayTextKey"
- :should-truncate-start="shouldTruncateStart"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
/>
diff --git a/app/assets/javascripts/vue_shared/components/file_row_header.vue b/app/assets/javascripts/vue_shared/components/file_row_header.vue
new file mode 100644
index 00000000000..301fd8116a9
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/file_row_header.vue
@@ -0,0 +1,25 @@
+<script>
+import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
+
+const MAX_PATH_LENGTH = 40;
+
+export default {
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ truncatedPath() {
+ return truncatePathMiddleToLength(this.path, MAX_PATH_LENGTH);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="file-row-header bg-white sticky-top p-2 js-file-row-header">
+ <span class="bold">{{ truncatedPath }}</span>
+ </div>
+</template>
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 46d40ea7aa5..ace46e32b18 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -101,3 +101,41 @@ body.modal-open {
margin: 0;
}
}
+
+.issues-import-modal,
+.issues-export-modal {
+ .modal-header {
+ justify-content: flex-start;
+
+ .import-export-svg-container {
+ flex-grow: 1;
+ height: 56px;
+ padding: $gl-btn-padding $gl-btn-padding 0;
+
+ > svg {
+ float: right;
+ height: 100%;
+ }
+ }
+ }
+
+ .modal-body {
+ padding: 0;
+
+ .modal-subheader {
+ justify-content: flex-start;
+ align-items: center;
+ border-bottom: 1px solid $modal-border-color;
+ padding: 14px;
+ }
+
+ .modal-text {
+ padding: $gl-padding-24 $gl-padding;
+ min-height: $modal-body-height;
+ }
+ }
+
+ .checkmark {
+ color: $green-400;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index d92d81b2cb5..242977e8543 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -656,6 +656,7 @@ $border-color-settings: #e1e1e1;
Modals
*/
$modal-body-height: 134px;
+$modal-border-color: #e9ecef;
$priority-label-empty-state-width: 114px;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 09235661cea..1352a004206 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -55,16 +55,16 @@
@include build-trace();
}
- .archived-sticky {
+ .archived-job {
top: $header-height;
border-radius: 2px 2px 0 0;
color: $orange-600;
background-color: $orange-100;
border: 1px solid $border-gray-normal;
- border-bottom: 0;
padding: 3px 12px;
margin: auto;
align-items: center;
+ z-index: 1;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index bb6b6f84849..6c847fc0d53 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -155,6 +155,14 @@ ul.related-merge-requests > li {
}
}
+.issues-nav-controls {
+ font-size: 0;
+
+ .btn-group:empty {
+ display: none;
+ }
+}
+
.issuable-email-modal-btn {
padding: 0;
color: $blue-600;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index b813eb16dad..a1e847009fc 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -249,6 +249,16 @@
.event-item {
padding-left: 40px;
}
+
+ @include media-breakpoint-up(lg) {
+ margin-right: 5px;
+ }
+ }
+
+ .projects-block {
+ @include media-breakpoint-up(lg) {
+ margin-left: 5px;
+ }
}
@include media-breakpoint-down(xs) {
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 0eea0cdd50f..c114e16edf8 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -7,12 +7,12 @@ module UploadsActions
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
def create
- link_to_file = UploadService.new(model, params[:file], uploader_class).execute
+ uploader = UploadService.new(model, params[:file], uploader_class).execute
respond_to do |format|
- if link_to_file
+ if uploader
format.json do
- render json: { link: link_to_file }
+ render json: { link: uploader.to_h }
end
else
format.json do
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5ed46fc0545..21688e54481 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,7 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
def self.issue_except_actions
- %i[index calendar new create bulk_update]
+ %i[index calendar new create bulk_update import_csv]
end
def self.set_issuables_index_only_actions
@@ -37,6 +37,8 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
+ before_action :authorize_import_issues!, only: [:import_csv]
+
before_action :set_suggested_issues_feature_flags, only: [:new]
respond_to :html
@@ -175,6 +177,20 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def import_csv
+ return render_404 unless Feature.enabled?(:issues_import_csv)
+
+ if uploader = UploadService.new(project, params[:file]).execute
+ ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
+
+ flash[:notice] = _("Your issues are being imported. Once finished, you'll get a confirmation email.")
+ else
+ flash[:alert] = _("File upload error.")
+ end
+
+ redirect_to project_issues_path(project)
+ end
+
protected
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index ae8ac61ad46..521ec2acebb 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -6,6 +6,8 @@ module Projects
before_action :check_license
before_action :authorize_update_environment!
+ helper_method :error_tracking_setting
+
def show
end
@@ -22,13 +24,18 @@ module Projects
private
+ def error_tracking_setting
+ @error_tracking_setting ||= project.error_tracking_setting ||
+ project.build_error_tracking_setting
+ end
+
def update_params
params.require(:project).permit(permitted_project_params)
end
# overridden in EE
def permitted_project_params
- {}
+ { error_tracking_setting_attributes: [:enabled, :api_url, :token] }
end
def check_license
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index af7a262e32c..e67c327f7f8 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -285,7 +285,7 @@ module ProjectsHelper
# overridden in EE
def settings_operations_available?
- false
+ Feature.enabled?(:error_tracking, @project) && can?(current_user, :read_environment, @project)
end
private
@@ -549,6 +549,7 @@ module ProjectsHelper
services#edit
repository#show
ci_cd#show
+ operations#show
badges#index
pages#show
]
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 370e6d2f90b..654ae211310 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -77,6 +77,17 @@ module Emails
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end
+ def import_issues_csv_email(user_id, project_id, results)
+ @user = User.find(user_id)
+ @project = Project.find(project_id)
+ @results = results
+
+ mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
+ end
+ end
+
private
def setup_issue_mail(issue_id, recipient_id)
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 2ac4610967d..80e0a17c312 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -76,6 +76,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.changed_milestone_issue_email(user.id, issue.id, milestone, user.id)
end
+ def import_issues_csv_email
+ Notify.import_issues_csv_email(user, project, { success: 3, errors: [5, 6, 7], valid_file: true })
+ end
+
def closed_merge_request_email
Notify.closed_merge_request_email(user.id, issue.id, user.id).message
end
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index e349f0fe971..885f61beb05 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -19,6 +19,12 @@ class CommitCollection
commits.each(&block)
end
+ def committers
+ emails = commits.reject(&:merge_commit?).map(&:committer_email).uniq
+
+ User.by_any_email(emails)
+ 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/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
new file mode 100644
index 00000000000..632c64c2f1c
--- /dev/null
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ProjectErrorTrackingSetting < ActiveRecord::Base
+ belongs_to :project
+
+ validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true }
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm'
+ end
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 613860ec31a..5310f2ee765 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -284,6 +284,14 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
+ def committers
+ @committers ||= commits.committers
+ end
+
+ def authors
+ User.from_union([committers, User.where(id: self.author_id)])
+ end
+
# Verifies if title has changed not taking into account WIP prefix
# for merge requests.
def wipless_title_changed(old_title)
@@ -327,13 +335,15 @@ class MergeRequest < ActiveRecord::Base
end
def commits
- if persisted?
- merge_request_diff.commits
- elsif compare_commits
- compare_commits.reverse
- else
- []
- end
+ return merge_request_diff.commits if persisted?
+
+ commits_arr = if compare_commits
+ compare_commits.reverse
+ else
+ []
+ end
+
+ CommitCollection.new(source_project, commits_arr, source_branch)
end
def commits_count
diff --git a/app/models/note.rb b/app/models/note.rb
index becf14e9785..1578ae9c4cc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -456,6 +456,10 @@ class Note < ActiveRecord::Base
Upload.find_by(model: self, path: paths)
end
+ def parent
+ project
+ end
+
private
def keep_around_commit
diff --git a/app/models/project.rb b/app/models/project.rb
index 8c8b4d3748a..a66ed6736ca 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -187,6 +187,7 @@ class Project < ActiveRecord::Base
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
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'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
@@ -295,6 +296,8 @@ class Project < ActiveRecord::Base
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
+ accepts_nested_attributes_for :error_tracking_setting, update_only: true
+
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
@@ -1786,6 +1789,24 @@ class Project < ActiveRecord::Base
handle_update_attribute_error(e, value)
end
+ # Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
+ #
+ # @return [Boolean] true when set to read_only or false when an existing git transfer is in progress
+ def set_repository_read_only!
+ with_lock do
+ break false if git_transfer_in_progress?
+
+ update_column(:repository_read_only, true)
+ end
+ end
+
+ # Set repository as writable again
+ def set_repository_writable!
+ with_lock do
+ update_column(repository_read_only, false)
+ end
+ end
+
def pushes_since_gc
Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i }
end
@@ -1900,15 +1921,17 @@ class Project < ActiveRecord::Base
def migrate_to_hashed_storage!
return unless storage_upgradable?
- update!(repository_read_only: true)
-
- if repo_reference_count > 0 || wiki_reference_count > 0
+ if git_transfer_in_progress?
ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
ProjectMigrateHashedStorageWorker.perform_async(id)
end
end
+ def git_transfer_in_progress?
+ repo_reference_count > 0 || wiki_reference_count > 0
+ end
+
def storage_version=(value)
super
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b19ae2e0e6a..b47238b52f1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1072,19 +1072,11 @@ class Repository
end
def cache
- @cache ||= if is_wiki
- Gitlab::RepositoryCache.new(self, extra_namespace: 'wiki')
- else
- Gitlab::RepositoryCache.new(self)
- end
+ @cache ||= Gitlab::RepositoryCache.new(self)
end
def request_store_cache
- @request_store_cache ||= if is_wiki
- Gitlab::RepositoryCache.new(self, extra_namespace: 'wiki', backend: Gitlab::SafeRequestStore)
- else
- Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
- end
+ @request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
end
def tags_sorted_by_committed_date
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 0d0f1c28bad..72de04203a6 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -7,6 +7,10 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
+ desc "User has access to all private groups & projects"
+ with_options scope: :user, score: 0
+ condition(:full_private_access) { @user&.full_private_access? }
+
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3146f26bed5..d70417e710e 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -222,6 +222,8 @@ class ProjectPolicy < BasePolicy
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
+ rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues
+
rule { can?(:developer_access) }.policy do
enable :admin_merge_request
enable :admin_milestone
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index 288bf070cfc..7dafa33bb99 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -5,13 +5,12 @@ class ProjectSnippetPolicy < BasePolicy
desc "Snippet is public"
condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:internal_snippet, scope: :subject) { @subject.internal? }
condition(:private_snippet, scope: :subject) { @subject.private? }
condition(:public_project, scope: :subject) { @subject.project.public? }
condition(:is_author) { @user && @subject.author == @user }
- condition(:internal, scope: :subject) { @subject.internal? }
-
# We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
rule { ~can?(:read_project) }.policy do
@@ -26,13 +25,13 @@ class ProjectSnippetPolicy < BasePolicy
# is used to hide/show various snippet-related controls, so we can't just move
# all of the handling here.
rule do
- all?(private_snippet | (internal & external_user),
+ all?(private_snippet | (internal_snippet & external_user),
~project.guest,
- ~admin,
- ~is_author)
+ ~is_author,
+ ~full_private_access)
end.prevent :read_project_snippet
- rule { internal & ~is_author & ~admin }.policy do
+ rule { internal_snippet & ~is_author & ~admin }.policy do
prevent :update_project_snippet
prevent :admin_project_snippet
end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 1db6c9eff36..44b6ca299ae 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -189,6 +189,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
merge_request.subscribed?(current_user, merge_request.target_project)
end
+ def conflicts_docs_path
+ help_page_path('user/project/merge_requests/resolve_conflicts.md')
+ end
+
private
def cached_can_be_reverted?
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 9731b52f1ad..9361c9f987b 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -11,6 +11,9 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_user_id
expose :merge_when_pipeline_succeeds
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)
+ end
expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
@@ -240,6 +243,10 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :supports_suggestion?, as: :can_receive_suggestion
+ expose :conflicts_docs_path do |merge_request|
+ presenter(merge_request).conflicts_docs_path
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
new file mode 100644
index 00000000000..ef08fafa7cc
--- /dev/null
+++ b/app/services/issues/import_csv_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Issues
+ class ImportCsvService
+ def initialize(user, project, csv_io)
+ @user = user
+ @project = project
+ @csv_io = csv_io
+ @results = { success: 0, error_lines: [], parse_error: false }
+ end
+
+ def execute
+ process_csv
+ email_results_to_user
+
+ @results
+ end
+
+ private
+
+ def process_csv
+ csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
+
+ CSV.new(csv_data, col_sep: detect_col_sep(csv_data.lines.first), headers: true).each.with_index(2) do |row, line_no|
+ issue = Issues::CreateService.new(@project, @user, title: row[0], description: row[1]).execute
+
+ if issue.persisted?
+ @results[:success] += 1
+ else
+ @results[:error_lines].push(line_no)
+ end
+ end
+ rescue ArgumentError, CSV::MalformedCSVError
+ @results[:parse_error] = true
+ end
+
+ def email_results_to_user
+ Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_now
+ end
+
+ def detect_col_sep(header)
+ if header.include?(",")
+ ","
+ elsif header.include?(";")
+ ";"
+ elsif header.include?("\t")
+ "\t"
+ else
+ raise CSV::MalformedCSVError
+ end
+ end
+ end
+end
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 4c14d834949..7ee9732040d 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -31,7 +31,7 @@ module Notes
return if command_params.empty?
return unless supported?(note)
- self.class.noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
+ self.class.noteable_update_service(note).new(note.parent, current_user, command_params).execute(note.noteable)
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 f3e026ba38c..2d851866a18 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -2,6 +2,8 @@
module Projects
module HashedStorage
+ RepositoryMigrationError = Class.new(StandardError)
+
class MigrateRepositoryService < BaseService
include Gitlab::ShellAdapter
@@ -16,6 +18,8 @@ module Projects
end
def execute
+ try_to_set_repository_read_only!
+
@old_storage_version = project.storage_version
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
project.ensure_storage_path_exists
@@ -48,6 +52,16 @@ module Projects
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
+
# rubocop: disable CodeReuse/ActiveRecord
def has_wiki?
gitlab_shell.exists?(project.repository_storage, "#{old_wiki_disk_path}.git")
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index 7ff0599ee95..abd6d8de750 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -12,7 +12,7 @@ module Projects
private
def project_update_params
- {}
+ params.slice(:error_tracking_setting_attributes)
end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index d248b10f41e..5c58caee8cd 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -2,6 +2,7 @@
module QuickActions
class InterpretService < BaseService
+ include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
attr_reader :issuable
@@ -210,15 +211,9 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- if project
- available_labels = LabelsFinder
- .new(current_user, project_id: project.id, include_ancestor_groups: true)
- .execute
- end
-
- project &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
- available_labels.any?
+ 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)
@@ -245,7 +240,7 @@ module QuickActions
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+ current_user.can?(:"admin_#{issuable.to_ability_name}", parent)
end
command :unlabel do |labels_param = nil|
if labels_param.present?
@@ -674,9 +669,25 @@ module QuickActions
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: [project.group&.id])).execute
end
- def find_labels(labels_param)
- extract_references(labels_param, :label) |
- LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
+ def parent
+ project || group
+ end
+
+ def group
+ strong_memoize(:group) do
+ issuable.group if issuable.respond_to?(:group)
+ end
+ end
+
+ def find_labels(labels_params = nil)
+ 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
+
+ result = LabelsFinder.new(current_user, finder_params).execute
+
+ extract_references(labels_params, :label) | result
end
def find_label_references(labels_param)
@@ -707,9 +718,11 @@ module QuickActions
# rubocop: disable CodeReuse/ActiveRecord
def extract_references(arg, type)
+ return [] unless arg
+
ext = Gitlab::ReferenceExtractor.new(project, current_user)
- ext.analyze(arg, author: current_user)
+ ext.analyze(arg, author: current_user, group: group)
ext.references(type)
end
diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb
index 39909ee4f82..41ca95b3b6f 100644
--- a/app/services/upload_service.rb
+++ b/app/services/upload_service.rb
@@ -11,7 +11,7 @@ class UploadService
uploader = @uploader_class.new(@model, nil, @uploader_context)
uploader.store!(@file)
- uploader.to_h
+ uploader
end
private
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index 5feb0b0f05b..d3e32818dc7 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -26,6 +26,7 @@
# - 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
@@ -70,7 +71,8 @@ class UrlValidator < ActiveModel::EachValidator
allow_localhost: true,
allow_local_network: true,
ascii_only: false,
- enforce_user: false
+ enforce_user: false,
+ enforce_sanitization: false
}
end
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index fdaad1cf181..c99d7e9b8e9 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -13,7 +13,7 @@
= 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'
.form-group
- = f.label :auto_devops_domain, class: 'label-bold'
+ = f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
= f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com'
.form-text.text-muted
= s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.")
diff --git a/app/views/notify/import_issues_csv_email.html.haml b/app/views/notify/import_issues_csv_email.html.haml
new file mode 100644
index 00000000000..f30d2b5f078
--- /dev/null
+++ b/app/views/notify/import_issues_csv_email.html.haml
@@ -0,0 +1,18 @@
+- text_style = 'font-size:16px; text-align:center; line-height:30px;'
+
+%p{ style: text_style }
+ Your CSV import for project
+ %a{ href: project_url(@project), style: "color:#3777b0; text-decoration:none;" }
+ = @project.full_name
+ has been completed.
+
+%p{ style: text_style }
+ #{pluralize(@results[:success], 'issue')} imported.
+
+- if @results[:error_lines].present?
+ %p{ style: text_style }
+ Errors found on line #{'number'.pluralize(@results[:error_lines].size)}: #{@results[:error_lines].join(', ')}. Please check if these lines have an issue title.
+
+- if @results[:parse_error]
+ %p{ style: text_style }
+ Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.
diff --git a/app/views/notify/import_issues_csv_email.text.erb b/app/views/notify/import_issues_csv_email.text.erb
new file mode 100644
index 00000000000..1117f90714d
--- /dev/null
+++ b/app/views/notify/import_issues_csv_email.text.erb
@@ -0,0 +1,11 @@
+Your CSV import for project <%= @project.full_name %> (<%= project_url(@project) %>) has been completed.
+
+<%= pluralize(@results[:success], 'issue') %> imported.
+
+<% if @results[:error_lines].present? %>
+Errors found on line <%= 'number'.pluralize(@results[:error_lines].size) %>: <%= @results[:error_lines].join(', ') %>. Please check if these lines have an issue title.
+<% end %>
+
+<% if @results[:parse_error] %>
+Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.
+<% end %>
diff --git a/app/views/projects/_issuable_by_email.html.haml b/app/views/projects/_issuable_by_email.html.haml
index c8ad5919596..147e73f047f 100644
--- a/app/views/projects/_issuable_by_email.html.haml
+++ b/app/views/projects/_issuable_by_email.html.haml
@@ -41,7 +41,7 @@
%a{ href: 'https://docs.gitlab.com/ee/development/emails.html#email-namespace', target: "_blank", rel: "noopener" }
%i.fa.fa-question-circle{ 'aria-label': "Learn more about incoming email addresses" }
- , generated just for you.
+ generated just for you.
Anyone who gets ahold of it can create issues or merge requests as if they were you.
You should
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index cecc139b183..888be4ee282 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -24,6 +24,6 @@
= _("No file selected")
= f.file_field :bfg_object_map, accept: 'text/plain', class: "hidden js-object-map-input", required: true
.form-text.text-muted
- = _("The maximum file size allowed is %{max_attachment_size}mb") % { max_attachment_size: Gitlab::CurrentSettings.max_attachment_size }
+ = _("The maximum file size allowed is %{size}.") % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
= f.submit _('Start cleanup'), class: 'btn btn-success'
diff --git a/app/views/projects/issues/_import_export.svg b/app/views/projects/issues/_import_export.svg
new file mode 100644
index 00000000000..53c35d12f57
--- /dev/null
+++ b/app/views/projects/issues/_import_export.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 238 111" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="4" width="82" rx="3" height="28" fill="#fff"/><path id="5" d="m68.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874" fill="#fc8a51"/><path id="6" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><circle id="2" cx="16" cy="14" r="7"/><circle id="0" cx="16" cy="14" r="7"/><mask id="3" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask><mask id="1" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask></defs><g fill="none" fill-rule="evenodd"><rect width="98" height="111" fill="#fff" rx="6"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 6.01v98.99c0 1.11.897 2.01 2 2.01h85.998c1.105 0 2-.897 2-2.01v-98.99c0-1.11-.897-2.01-2-2.01h-85.998c-1.105 0-2 .897-2 2.01m-4 0c0-3.318 2.685-6.01 6-6.01h85.998c3.314 0 6 2.689 6 6.01v98.99c0 3.318-2.685 6.01-6 6.01h-85.998c-3.314 0-6-2.689-6-6.01v-98.99"/><rect width="76" height="85" x="11" y="12" fill="#f9f9f9" rx="3"/><g transform="translate(37 59)"><use xlink:href="#4"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#1)" xlink:href="#0"/><use xlink:href="#5"/></g><g transform="translate(140)"><path fill="#fff" d="m0 4h94v103h-94z"/><path fill="#e5e5e5" fill-rule="nonzero" d="m0 74v30.993c0 3.318 2.687 6.01 6 6.01h85.998c3.316 0 6-2.69 6-6.01v-98.99c0-3.318-2.687-6.01-6-6.01h-85.998c-3.316 0-6 2.69-6 6.01v.993h4v-.993c0-1.11.896-2.01 2-2.01h85.998c1.105 0 2 .897 2 2.01v98.99c0 1.11-.896 2.01-2 2.01h-85.998c-1.105 0-2-.897-2-2.01v-30.993h-4"/><g fill="#f9f9f9"><rect width="82" height="28" x="8" y="12" rx="3"/><rect width="82" height="28" x="8" y="43" rx="3"/></g></g><g fill-rule="nonzero" transform="translate(148 73)"><use fill="#e5e5e5" xlink:href="#6"/><path fill="#6b4fbb" d="m17 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7"/></g><g transform="translate(25 24)"><use xlink:href="#4"/><use fill="#e5e5e5" fill-rule="nonzero" xlink:href="#6"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#3)" xlink:href="#2"/><use xlink:href="#5"/></g><g transform="translate(107 10)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><path fill="#6b4fbb" fill-rule="nonzero" d="m16 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7" id="7"/><use xlink:href="#5"/></g><g transform="translate(128 41)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><use xlink:href="#7"/><path fill="#fc8a51" d="m66.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874"/></g></g></svg> \ No newline at end of file
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index e4a0d4b8479..fd6559e37ba 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,11 +1,30 @@
-= render 'shared/issuable/feed_buttons'
-
-- if @can_bulk_update
- = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
-- if show_new_issue_link?(@project)
- = link_to "New issue", new_project_issue_path(@project,
- issue: { assignee_id: finder.assignee.try(:id),
- milestone_id: finder.milestones.first.try(:id) }),
- class: "btn btn-success",
- title: "New issue",
- id: "new_issue_link"
+- show_feed_buttons = local_assigns.fetch(:show_feed_buttons, true)
+- show_import_button = local_assigns.fetch(:show_import_button, true) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
+- show_export_button = local_assigns.fetch(:show_export_button, true)
+
+.nav-controls.issues-nav-controls
+ - if show_feed_buttons
+ = render 'shared/issuable/feed_buttons'
+
+ .btn-group.append-right-10<
+ - if show_export_button
+ = render_if_exists 'projects/issues/export_csv/button'
+
+ - if show_import_button
+ = render 'projects/issues/import_csv/button'
+
+ - if @can_bulk_update
+ = button_tag _("Edit issues"), class: "btn btn-default append-right-10 js-bulk-update-toggle"
+ - if show_new_issue_link?(@project)
+ = link_to _("New issue"), new_project_issue_path(@project,
+ issue: { assignee_id: finder.assignee.try(:id),
+ milestone_id: finder.milestones.first.try(:id) }),
+ class: "btn btn-success",
+ title: _("New issue"),
+ id: "new_issue_link"
+
+- if show_export_button
+ = render_if_exists 'projects/issues/export_csv/modal'
+
+- if show_import_button
+ = render 'projects/issues/import_csv/modal'
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
new file mode 100644
index 00000000000..acc2c50294f
--- /dev/null
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -0,0 +1,9 @@
+- type = local_assigns.fetch(:type, :icon)
+
+%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
+ data: { toggle: 'modal', target: '.issues-import-modal' } }
+ - if type == :icon
+ = sprite_icon('upload')
+ - else
+ = _('Import CSV')
+
diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml
new file mode 100644
index 00000000000..5339c4325b9
--- /dev/null
+++ b/app/views/projects/issues/import_csv/_modal.html.haml
@@ -0,0 +1,24 @@
+.issues-import-modal.modal
+ .modal-dialog
+ .modal-content
+ = form_tag import_csv_namespace_project_issues_path, multipart: true do
+ .modal-header
+ %h3
+ = _('Import issues')
+ .import-export-svg-container
+ = render 'projects/issues/import_export.svg'
+ %a.close{ href: '#', 'data-dismiss' => 'modal' } ×
+ .modal-body
+ .modal-text
+ %p
+ = _("Your issues will be imported in the background. Once finished, you'll get a confirmation email.")
+ .form-group
+ = label_tag :file, _('Upload CSV file'), class: 'label-bold'
+ %div
+ = file_field_tag :file, accept: '.csv,text/csv', required: true
+ %p.text-secondary
+ = _('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.')
+ = _('The maximum file size allowed is %{size}.') % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
+ .modal-footer
+ %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button"} }
+ = _('Import issues')
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 1e7737aeb97..39e9e9171cf 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -11,8 +11,7 @@
%div{ class: (container_class) }
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls
- = render "projects/issues/nav_btns"
+ = render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
@@ -23,4 +22,4 @@
- if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
- = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
+ = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index a760d02c4c3..8275996b522 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -33,7 +33,7 @@
%span.d-block.d-sm-none Blank
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Create from template
+ %span.d-none.d-sm-block.qa-project-create-from-template-tab Create from template
%span.d-block.d-sm-none Template
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
new file mode 100644
index 00000000000..71335e4dfd0
--- /dev/null
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -0,0 +1,30 @@
+- return unless Feature.enabled?(:error_tracking, @project) && can?(current_user, :read_environment, @project)
+
+- setting = error_tracking_setting
+
+%section.settings.expanded.border-0.no-animate
+ .settings-header
+ %h4
+ = _('Error Tracking')
+ %p
+ = _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
+ .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}/issues/')
+ %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'
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 0782029dbcd..b36fa9a5f51 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -1,4 +1,5 @@
- @content_class = 'limit-container-width' unless fluid_layout
- page_title _('Operations')
+= render 'projects/settings/operations/error_tracking', expanded: true
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 0ddc56dc6c3..0434860dec4 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -1,5 +1,6 @@
- button_path = local_assigns.fetch(:button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
+- show_import_button = local_assigns.fetch(:show_import_button, false) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
.row.empty-state
@@ -21,12 +22,20 @@
- if has_button
.text-center
- if project_select_button
- = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues, with_feature_enabled: 'issues'
+ = render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues'
- else
- = link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
+ = link_to _('New issue'), button_path, class: 'btn btn-success', title: _('New issue'), id: 'new_issue_link'
+
+ - if show_import_button
+ = render 'projects/issues/import_csv/button', type: :text
+
- else
%h4.text-center= _("There are no issues to show")
%p
= _("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.")
.text-center
= link_to _('Register / Sign In'), new_user_session_path, class: 'btn btn-success'
+
+- if show_import_button
+ = render 'projects/issues/import_csv/modal'
+
diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
index d4834090413..83f60fa6fe2 100644
--- a/app/views/shared/issuable/_feed_buttons.html.haml
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -1,4 +1,4 @@
-= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do
+= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
= icon('rss')
-= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do
+= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
= custom_icon('icon_calendar')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index c5cce1823f0..1a59055f652 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -25,7 +25,7 @@
.value.hide-collapsed
- if issuable_sidebar[:assignee]
= link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
- - if issuable_sidebar[:assignee][:can_merge]
+ - 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
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 1881875b7c0..ca3141b2cc3 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -19,4 +19,4 @@
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
= label_tag 'merge_request[squash]', class: 'form-check-label' do
Squash commits when merge request is accepted.
- = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')
+ = link_to icon('question-circle'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank'
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 0674c822d63..f487c0bf0d5 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,30 +1,31 @@
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
- %input.btn.btn-nr.btn-success.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
+ %input.btn.btn-nr.btn-success.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment') }
- if @note.can_be_discussion_note?
- = button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
+ = button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= icon('caret-down', class: 'toggle-icon')
%ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
- %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
+ %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
- %strong Comment
+ %strong= _("Comment")
%p
- Add a general comment to this #{noteable_name}.
+ = _("Add a general comment to this %{noteable_name}.") % { noteable_name: noteable_name }
%li.divider.droplab-item-ignore
- %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
+ %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start discussion'), 'close-text' => _("Start discussion & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start discussion & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
- %strong Start discussion
+ %strong= _("Start discussion")
%p
= succeed '.' do
- Discuss a specific suggestion or question
- if @note.noteable.supports_resolvable_notes?
- that needs to be resolved
+ = _('Discuss a specific suggestion or question that needs to be resolved')
+ - else
+ = _('Discuss a specific suggestion or question')
diff --git a/app/views/shared/notes/_edit.html.haml b/app/views/shared/notes/_edit.html.haml
index f4b3aac29b4..84a3ef9d8fe 100644
--- a/app/views/shared/notes/_edit.html.haml
+++ b/app/views/shared/notes/_edit.html.haml
@@ -1 +1 @@
-%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: note_url(note) } }= note.note
+%textarea.hidden.js-task-list-field.original-task-list{ data: { update_url: note_url(note) } }= note.note
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index fec966069b9..6fe511c2999 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -3,12 +3,12 @@
= hidden_field_tag :target_id, '', class: 'js-form-target-id'
= hidden_field_tag :target_type, '', class: 'js-form-target-type'
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do
- = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here…"
+ = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: _("Write a comment or drag your files here…")
= render 'shared/notes/hints'
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
- Finish editing this message first!
- = submit_tag 'Save comment', class: 'btn btn-nr btn-success js-comment-save-button'
+ = _("Finish editing this message first!")
+ = submit_tag _('Save comment'), class: 'btn btn-nr btn-success js-comment-save-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
- Cancel
+ = _("Cancel")
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 493c6241257..6a1eea85fde 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -29,7 +29,7 @@
= render 'projects/zen', f: f,
attr: :note,
classes: 'note-textarea js-note-text',
- placeholder: "Write a comment or drag your files here…",
+ placeholder: _("Write a comment or drag your files here…"),
supports_quick_actions: supports_quick_actions,
supports_autocomplete: supports_autocomplete
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
@@ -40,5 +40,5 @@
= yield(:note_actions)
- %a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: {cancel_text: "Cancel" } }
- Cancel
+ %a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } }
+ = _('Cancel')
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 00eae553279..46f3f8428f1 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,10 +1,10 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.comment-toolbar.clearfix
.toolbar-text
- = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
+ = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', tabindex: -1
- if supports_quick_actions
and
- = link_to 'quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
+ = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
are
- else
is
@@ -24,12 +24,12 @@
= icon('file-image-o', class: 'toolbar-button-icon')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
- %button.retry-uploading-link{ type: 'button' } Try again
+ %button.retry-uploading-link{ type: 'button' }= _("Try again")
or
- %button.attach-new-file.markdown-selector{ type: 'button' } attach a new file
+ %button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file")
%button.markdown-selector.button-attach-file{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+ = _("Attach a file")
- %button.btn.btn-default.btn-sm.hide.button-cancel-uploading-files{ type: 'button' } Cancel
+ %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 e125d7f108a..cb5c64fb91d 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -62,7 +62,7 @@
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system
.system-note-commit-list-toggler.hide
- Toggle commit list
+ = _("Toggle commit list")
%i.fa.fa-angle-down
- if note.attachment.url
.note-attachment
@@ -74,5 +74,5 @@
= icon('paperclip')
= note.attachment_identifier
= link_to delete_attachment_project_note_path(note.project, note),
- title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
+ title: _('Delete this attachment'), method: :delete, remote: true, data: { confirm: _('Are you sure you want to remove the attachment?') }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 4c4050c6054..002189e6ecd 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -19,20 +19,14 @@
= render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete
- elsif !current_user
.disabled-comment.text-center.prepend-top-default
- Please
- = link_to "register", new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), class: 'js-register-link'
- or
- = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link'
- to comment
+ - link_to_register = link_to(_("register"), new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), class: 'js-register-link')
+ - link_to_sign_in = link_to(_("sign in"), new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link')
+ = _("Please %{link_to_register} or %{link_to_sign_in} to comment").html_safe % { link_to_register: link_to_register, link_to_sign_in: link_to_sign_in }
- elsif discussion_locked
.disabled-comment.text-center.prepend-top-default
%span.issuable-note-warning
= sprite_icon('lock', size: 16, css_class: 'icon')
%span
- This
- = issuable.class.to_s.titleize.downcase
- is locked. Only
- %b project members
- can comment.
+ = _("This %{issuable} is locked. Only <strong>project members</strong> can comment.").html_safe % { issuable: issuable.class.to_s.titleize.downcase }
-# haml-lint:disable InlineJavaScript
%script.js-notes-data{ type: "application/json" }= initial_notes_data(autocomplete).to_json.html_safe
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index d22905ecc93..b5bc1180290 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -9,24 +9,22 @@
.col-md-12.col-lg-6
- if can?(current_user, :read_cross_project)
.activities-block
- .append-right-5
- .prepend-top-16
- .d-flex.align-items-center.border-bottom
- %h4.flex-grow
- = s_('UserProfile|Activity')
- = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_path } }
- .center.light.loading
- = spinner nil, true
-
- .col-md-12.col-lg-6
- .projects-block
- .prepend-left-5
.prepend-top-16
.d-flex.align-items-center.border-bottom
%h4.flex-grow
- = s_('UserProfile|Personal projects')
- = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_projects_path } }
+ = s_('UserProfile|Activity')
+ = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_path } }
.center.light.loading
= spinner nil, true
+
+ .col-md-12.col-lg-6
+ .projects-block
+ .prepend-top-16
+ .d-flex.align-items-center.border-bottom
+ %h4.flex-grow
+ = s_('UserProfile|Personal projects')
+ = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_projects_path } }
+ .center.light.loading
+ = spinner nil, true
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index d3cf21db335..223ddc80c88 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -140,3 +140,4 @@
- detect_repository_languages
- repository_cleanup
- delete_stored_files
+- import_issues_csv
diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb
new file mode 100644
index 00000000000..d44fdfec8ae
--- /dev/null
+++ b/app/workers/import_issues_csv_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ImportIssuesCsvWorker
+ include ApplicationWorker
+
+ sidekiq_retries_exhausted do |job|
+ Upload.find(job['args'][2]).destroy
+ end
+
+ def perform(current_user_id, project_id, upload_id)
+ @user = User.find(current_user_id)
+ @project = Project.find(project_id)
+ @upload = Upload.find(upload_id)
+
+ importer = Issues::ImportCsvService.new(@user, @project, @upload.build_uploader)
+ importer.execute
+
+ @upload.destroy
+ end
+end
diff --git a/bin/secpick b/bin/secpick
index 11acdd82226..3d032f696a2 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
+
# frozen_string_literal: false
require 'active_support/core_ext/object/to_query'
@@ -7,69 +8,132 @@ require 'open3'
require 'rainbow/refinement'
using Rainbow
-BRANCH_PREFIX = 'security'.freeze
-REMOTE = 'dev'.freeze
-NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze
-
-options = { version: nil, branch: nil, sha: nil }
-
-parser = OptionParser.new do |opts|
- opts.banner = "Usage: #{$0} [options]"
- opts.on('-v', '--version 10.0', 'Version') do |version|
- options[:version] = version&.tr('.', '-')
- end
-
- opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
- options[:branch] = branch
- end
-
- opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
- options[:sha] = sha
- end
-
- opts.on('-h', '--help', 'Displays Help') do
- puts opts
-
- exit
+module Secpick
+ BRANCH_PREFIX = 'security'.freeze
+ DEFAULT_REMOTE = 'dev'.freeze
+ NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze
+
+ class SecurityFix
+ def initialize
+ @options = self.class.options
+ end
+
+ def ee?
+ File.exist?('./CHANGELOG-EE.md')
+ end
+
+ def dry_run?
+ @options[:try] == true
+ end
+
+ def original_branch
+ @options[:branch].strip
+ end
+
+ def source_branch
+ branch = "#{original_branch}-#{@options[:version]}"
+ branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
+ branch.freeze
+ end
+
+ def security_branch
+ "#{BRANCH_PREFIX}-#{@options[:version]}".tap do |name|
+ name << "-ee" if ee?
+ end.freeze
+ end
+
+ def git_commands
+ ["git fetch #{@options[:remote]} #{security_branch}",
+ "git checkout #{security_branch}",
+ "git pull #{@options[:remote]} #{security_branch}",
+ "git checkout -B #{source_branch}",
+ "git cherry-pick #{@options[:sha]}",
+ "git push #{@options[:remote]} #{source_branch}",
+ "git checkout #{original_branch}"]
+ end
+
+ def gitlab_params
+ {
+ merge_request: {
+ source_branch: source_branch,
+ target_branch: security_branch,
+ title: "WIP: [#{@options[:version].tr('-', '.')}] ",
+ description: '/label ~security'
+ }
+ }
+ end
+
+ def new_mr_url
+ if ee?
+ NEW_MR_URL.sub('gitlabhq', 'gitlab-ee')
+ else
+ NEW_MR_URL
+ end
+ end
+
+ def create!
+ if dry_run?
+ puts git_commands.join("\n").green
+ puts "\nMerge request params: ".blue
+ pp gitlab_params
+ else
+ cmd = git_commands.join(' && ')
+ stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
+
+ puts stdout.read&.green
+ puts stderr.read&.red
+
+ if wait_thr.value.success?
+ puts "#{new_mr_url}?#{gitlab_params.to_query}".blue
+ end
+
+ stdin.close
+ stdout.close
+ stderr.close
+ end
+ end
+
+ def self.options
+ { version: nil, branch: nil, sha: nil }.tap do |options|
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options]"
+ opts.on('-v', '--version 10.0', 'Version') do |version|
+ options[:version] = version&.tr('.', '-')
+ end
+
+ opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
+ options[:branch] = branch
+ end
+
+ opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
+ options[:sha] = sha
+ end
+
+ opts.on('-r', '--remote abcd', 'Git remote name of dev.gitlab.org (optional, defaults to `dev`)') do |remote|
+ options[:remote] = remote
+ end
+
+ opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do |remote|
+ options[:try] = true
+ end
+
+ opts.on('-h', '--help', 'Displays Help') do
+ puts opts
+
+ exit
+ end
+ end
+
+ parser.parse!
+
+ options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
+ options[:remote] ||= DEFAULT_REMOTE
+
+ abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
+ abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
+ end
+ end
end
end
-parser.parse!
-
-options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
-
-abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
-abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
-
-ee = File.exist?('./CHANGELOG-EE.md')
-original_branch = options[:branch].strip
-branch = "#{original_branch}-#{options[:version]}"
-branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
-branch = branch.freeze
-stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".tap do |name|
- name << "-ee" if ee
-end.freeze
-
-command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch} && git checkout #{original_branch}"
-
-stdin, stdout, stderr, wait_thr = Open3.popen3(command)
-
-puts stdout.read&.green
-puts stderr.read&.red
-
-if wait_thr.value.success?
- params = {
- merge_request: {
- source_branch: branch,
- target_branch: stable_branch,
- title: "WIP: [#{options[:version].tr('-', '.')}] ",
- description: '/label ~security'
- }
- }
-
- puts "#{NEW_MR_URL}?#{params.to_query}".blue
-end
-
-stdin.close
-stdout.close
-stderr.close
+Secpick::SecurityFix.new.create!
diff --git a/changelogs/unreleased/49231-import-issues-csv.yml b/changelogs/unreleased/49231-import-issues-csv.yml
new file mode 100644
index 00000000000..c10bd8143b2
--- /dev/null
+++ b/changelogs/unreleased/49231-import-issues-csv.yml
@@ -0,0 +1,5 @@
+---
+title: Add importing of issues from CSV file
+merge_request: 23532
+author:
+type: added
diff --git a/changelogs/unreleased/53966-hashed-storage-read-only.yml b/changelogs/unreleased/53966-hashed-storage-read-only.yml
new file mode 100644
index 00000000000..2b6c9c49c85
--- /dev/null
+++ b/changelogs/unreleased/53966-hashed-storage-read-only.yml
@@ -0,0 +1,5 @@
+---
+title: 'Hashed Storage: Only set as `read_only` when starting the per-project migration'
+merge_request: 24128
+author:
+type: changed
diff --git a/changelogs/unreleased/55192-about-link-in-new-window.yml b/changelogs/unreleased/55192-about-link-in-new-window.yml
new file mode 100644
index 00000000000..b686150942b
--- /dev/null
+++ b/changelogs/unreleased/55192-about-link-in-new-window.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve About this feature link should open in new window
+merge_request: 24149
+author:
+type: fixed
diff --git a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
new file mode 100644
index 00000000000..407346bbf22
--- /dev/null
+++ b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Remove horizontal whitespace on user profile overview on small breakpoints
+merge_request: 24189
+author:
+type: other
diff --git a/changelogs/unreleased/56019-archived-stuck.yml b/changelogs/unreleased/56019-archived-stuck.yml
new file mode 100644
index 00000000000..de3698a327b
--- /dev/null
+++ b/changelogs/unreleased/56019-archived-stuck.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes z-index and margins of archived alert in job page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/56076-releases-margin.yml b/changelogs/unreleased/56076-releases-margin.yml
new file mode 100644
index 00000000000..a3cae1e035f
--- /dev/null
+++ b/changelogs/unreleased/56076-releases-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes missing margin in releases block
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/custom-helm-chart-repo.yml b/changelogs/unreleased/custom-helm-chart-repo.yml
new file mode 100644
index 00000000000..592d2f60ca2
--- /dev/null
+++ b/changelogs/unreleased/custom-helm-chart-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Added feature to specify a custom Auto DevOps chart repository
+merge_request: 24162
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
new file mode 100644
index 00000000000..bb0b193a846
--- /dev/null
+++ b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes Auto DevOps title on CI/CD admin settings
+merge_request: 24249
+author:
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-shared-notes.yml b/changelogs/unreleased/gt-externalize-app-views-shared-notes.yml
new file mode 100644
index 00000000000..39ca6b67a54
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-shared-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/shared/notes`
+merge_request: 23696
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
new file mode 100644
index 00000000000..b01962591c6
--- /dev/null
+++ b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
@@ -0,0 +1,5 @@
+---
+title: Add folder header to files in merge request tree list
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
new file mode 100644
index 00000000000..b2ac53312ae
--- /dev/null
+++ b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Adjusts duplicated line when commenting on unfolded diff lines (in the bottom)
+merge_request: 24201
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-smooshpack.yml b/changelogs/unreleased/update-smooshpack.yml
new file mode 100644
index 00000000000..a9222088d53
--- /dev/null
+++ b/changelogs/unreleased/update-smooshpack.yml
@@ -0,0 +1,5 @@
+---
+title: Upgraded Codesandbox smooshpack package
+merge_request:
+author:
+type: other
diff --git a/config/routes/project.rb b/config/routes/project.rb
index f50bf5ab76f..cf5a57300cf 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -361,6 +361,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
collection do
post :bulk_update
+ post :import_csv
end
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 3ee32678f34..3e8c218052d 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -85,3 +85,4 @@
- [repository_cleanup, 1]
- [delete_stored_files, 1]
- [remote_mirror_notification, 2]
+ - [import_issues_csv, 2]
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index abb36098629..6a5a75b6eba 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -178,7 +178,7 @@ def lint_commits(commits)
failures = true
end
- if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+))
+ if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+\b))
fail_commit(
commit,
'Use full URLs instead of short references ' \
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index aa8686ac7d8..9a5f7cf8175 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,94 +1,136 @@
require './spec/support/sidekiq'
+# rubocop:disable Rails/Output
+
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
- project_urls = [
- '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'
- ]
-
- # 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|
- 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
+ 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
+ ]
- project_path.gsub!(".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
+ ]
- 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
- }
+ def create_project(url, force_latest_storage: false)
+ group_path, project_path = url.split('/')[-2..-1]
- if i % 2 == 0
- params[:storage_version] = Project::LATEST_STORAGE_VERSION
- end
+ 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
- project = nil
+ Sidekiq::Worker.skipping_transaction_check do
+ project = Projects::CreateService.new(User.first, params).execute
- 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
- # 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)
+ if project.valid? && project.valid_repo?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
end
- if project.valid? && project.valid_repo?
- print '.'
- else
- puts project.errors.full_messages
- print 'F'
+ # 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
diff --git a/db/migrate/20181212171634_create_error_tracking_settings.rb b/db/migrate/20181212171634_create_error_tracking_settings.rb
new file mode 100644
index 00000000000..18c38bd2c47
--- /dev/null
+++ b/db/migrate/20181212171634_create_error_tracking_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_error_tracking_settings, id: :int, primary_key: :project_id, default: nil do |t|
+ t.boolean :enabled, null: false, default: true
+ t.string :api_url, null: false
+ t.string :encrypted_token
+ t.string :encrypted_token_iv
+ t.foreign_key :projects, column: :project_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 12e4ed6d627..87826881d58 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1573,6 +1573,13 @@ ActiveRecord::Schema.define(version: 20190103140724) 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|
+ t.boolean "enabled", default: true, null: false
+ t.string "api_url", null: false
+ t.string "encrypted_token"
+ t.string "encrypted_token_iv"
+ end
+
create_table "project_features", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "merge_requests_access_level"
@@ -2434,6 +2441,7 @@ ActiveRecord::Schema.define(version: 20190103140724) do
add_foreign_key "project_custom_attributes", "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
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 621d4f77d5e..0d03b481881 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
@@ -255,7 +255,7 @@ After configuring LDAP, basic authentication will be available. Users can then l
Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../ldap.md#security) on LDAP security.
-If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
+If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `` jon.doe@example.com `` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
## LDAP extended features on GitLab EE
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 833c1f367dd..987a0b9f350 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -545,10 +545,10 @@ discussed in [Redis setup overview](#redis-setup-overview) and
Here is a list and description of each **machine** and the assigned **IP**:
-* `10.0.0.1`: Redis Master + Sentinel 1
-* `10.0.0.2`: Redis Slave 1 + Sentinel 2
-* `10.0.0.3`: Redis Slave 2 + Sentinel 3
-* `10.0.0.4`: GitLab application
+- `10.0.0.1`: Redis Master + Sentinel 1
+- `10.0.0.2`: Redis Slave 1 + Sentinel 2
+- `10.0.0.3`: Redis Slave 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 2101d36d2b6..14e2784c419 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -214,10 +214,10 @@ For this example, **Sentinel 1** will be configured in the same machine as the
Here is a list and description of each **machine** and the assigned **IP**:
-* `10.0.0.1`: Redis Master + Sentinel 1
-* `10.0.0.2`: Redis Slave 1 + Sentinel 2
-* `10.0.0.3`: Redis Slave 2 + Sentinel 3
-* `10.0.0.4`: GitLab application
+- `10.0.0.1`: Redis Master + Sentinel 1
+- `10.0.0.2`: Redis Slave 1 + Sentinel 2
+- `10.0.0.3`: Redis Slave 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index bb621b788f1..058346df56d 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -17,19 +17,18 @@ The housekeeping function will run a `repack` or `gc` depending on the
For example in the following scenario a `git repack -d` will be executed:
-+ Project: pushes since gc counter (`pushes_since_gc`) = `10`
-+ Git GC period = `200`
-+ Full repack period = `50`
+- Project: pushes since gc counter (`pushes_since_gc`) = `10`
+- Git GC period = `200`
+- Full repack period = `50`
When the `pushes_since_gc` value is 50 a `repack -A -d --pack-kept-objects` will run, similarly when
the `pushes_since_gc` value is 200 a `git gc` will be run.
-+ `git gc` ([man page][man-gc]) runs a number of housekeeping tasks,
-such as compressing filerevisions (to reduce disk space and increase performance)
-and removing unreachable objects which may have been created from prior invocations of
-`git add`.
-
-+ `git repack` ([man page][man-repack]) re-organize existing packs into a single, more efficient pack.
+- `git gc` ([man page][man-gc]) runs a number of housekeeping tasks,
+ such as compressing filerevisions (to reduce disk space and increase performance)
+ and removing unreachable objects which may have been created from prior invocations of
+ `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**.
@@ -39,4 +38,4 @@ You can find this option under your **[Project] > Edit Project**.
[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request"
[man-gc]: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html "git gc man page"
-[man-repack]: https://www.kernel.org/pub/software/scm/git/docs/git-repack.html \ No newline at end of file
+[man-repack]: https://www.kernel.org/pub/software/scm/git/docs/git-repack.html
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 27a3710632d..05873e01a08 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -13,43 +13,45 @@ GitLab has several features based on receiving incoming emails:
## Requirements
-Handling incoming emails requires an [IMAP]-enabled email account. GitLab
-requires one of the following three strategies:
+Handling incoming emails requires an [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol)-enabled
+email account. GitLab requires one of the following three strategies:
-- Email sub-addressing
-- Dedicated email address
+- Email sub-addressing (recommended)
- Catch-all mailbox
+- Dedicated email address (supports Reply by Email only)
Let's walk through each of these options.
-**If your provider or server supports email sub-addressing, we recommend using it.
-Most features (other than reply by email) only work with sub-addressing.**
-
-[IMAP]: https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol
-
### Email sub-addressing
[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is
-a feature where any email to `user+some_arbitrary_tag@example.com` will end up
-in the mailbox for `user@example.com`, and is supported by providers such as
-Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the
-[Postfix mail server] which you can run on-premises.
-
-[Postfix mail server]: reply_by_email_postfix_setup.md
+a mail server feature where any email to `user+arbitrary_tag@example.com` will end up
+in the mailbox for `user@example.com` . It is supported by providers such as
+Gmail, Google Apps, Yahoo! Mail, Outlook.com, and iCloud, as well as the
+[Postfix mail server](reply_by_email_postfix_setup.md), which you can run on-premises.
+
+TIP: **Tip:**
+If your provider or server supports email sub-addressing, we recommend using it.
+A dedicated email address only supports Reply by Email functionality.
+A catch-all mailbox supports the same features as sub-addressing as of GitLab 11.7,
+but sub-addressing is still preferred because only one email address is used,
+leaving a catch-all available for other purposes beyond GitLab.
-### Dedicated email address
+### Catch-all mailbox
-This solution is really simple to set up: you just have to create an email
-address dedicated to receive your users' replies to GitLab notifications.
+A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain
+receives all emails addressed to the domain that do not match any addresses that
+exist on the mail server.
-### Catch-all mailbox
+As of GitLab 11.7, catch-all mailboxes support the same features as
+email sub-addressing, but email sub-addressing remains our recommendation so that you
+can reserve your catch-all mailbox for other purposes.
-A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will
-"catch all" the emails addressed to the domain that do not exist in the mail
-server.
+### Dedicated email address
-GitLab can be set up to allow users to comment on issues and merge requests by
-replying to notification emails.
+This solution is relatively simple to set up: you just need to create an email
+address dedicated to receive your users' replies to GitLab notifications. However,
+this method only supports replies, and not the other features of [incoming email](#incoming-email).
## Set it up
@@ -160,14 +162,16 @@ for a real-world example of this exploit.
gitlab_rails['incoming_email_idle_timeout'] = 60
```
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes
- mailbox incoming@exchange.example.com
+ Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
+ catch-all mailbox incoming@exchange.example.com
```ruby
gitlab_rails['incoming_email_enabled'] = true
- # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here
- gitlab_rails['incoming_email_address'] = "incoming@exchange.example.com"
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+ gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com"
# Email account username
# Typically this is the userPrincipalName (UPN)
@@ -279,15 +283,17 @@ for a real-world example of this exploit.
idle_timeout: 60
```
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes
- mailbox incoming@exchange.example.com
+ Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
+ catch-all mailbox incoming@exchange.example.com
```yaml
incoming_email:
enabled: true
- # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here
- address: "incoming@exchange.example.com"
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+ address: "incoming-%{key}@exchange.example.com"
# Email account username
# Typically this is the userPrincipalName (UPN)
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index fa58d0ef15f..40e03093743 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -14,20 +14,33 @@ 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).
In brief:
-* GitLab relies on the user to provide their own Kubernetes credentials, and to
+- GitLab relies on the user to provide their own Kubernetes credentials, and to
appropriately label the pods they create when deploying.
-* When a user navigates to the terminal page for an environment, they are served
+- When a user navigates to the terminal page for an environment, they are served
a JavaScript application that opens a WebSocket connection back to GitLab.
-* The WebSocket is handled in [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse),
+- The WebSocket is handled in [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse),
rather than the Rails application server.
-* Workhorse queries Rails for connection details and user permissions; Rails
- queries Kubernetes for them in the background, using [Sidekiq](../troubleshooting/sidekiq.md)
-* Workhorse acts as a proxy server between the user's browser and the Kubernetes
+- Workhorse queries Rails for connection details and user permissions. Rails
+ queries Kubernetes for them in the background using [Sidekiq](../troubleshooting/sidekiq.md).
+- Workhorse acts as a proxy server between the user's browser and the Kubernetes
API, passing WebSocket frames between the two.
-* Workhorse regularly polls Rails, terminating the WebSocket connection if the
+- Workhorse regularly polls Rails, terminating the WebSocket connection if the
user no longer has permission to access the terminal, or if the connection
details have changed.
+## Security
+
+GitLab and [GitLab Runner](https://docs.gitlab.com/runner/) take some
+precautions to keep interactive web terminal data encrypted between them, and
+everything protected with authorization guards. This is described in more
+detail below.
+
+- Interactive web terminals are completely disabled unless [`[session_server]`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) is configured.
+- Every time the runner starts, it will generate an `x509` certificate that will be used for a `wss` (Web Socket Secure) connection.
+- For every created job, a random URL is generated which is discarded at the end of the job. This URL is used to establish a web socket connection. The URL for the session is in the format `(IP|HOST):PORT/session/$SOME_HASH`, where the `IP/HOST` and `PORT` are the configured [`listen_address`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section).
+- Every session URL that is created has an authorization header that needs to be sent, to establish a `wss` connection.
+- The session URL is not exposed to the users in any way. GitLab holds all the state internally and proxies accordingly.
+
## Enabling and disabling terminal support
As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
@@ -40,10 +53,10 @@ However, if you run a [load balancer](../high_availability/load_balancer.md) in
front of GitLab, you may need to make some changes to your configuration. These
guides document the necessary steps for a selection of popular reverse proxies:
-* [Apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html)
-* [NGINX](https://www.nginx.com/blog/websocket-nginx/)
-* [HAProxy](http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/)
-* [Varnish](https://www.varnish-cache.org/docs/4.1/users-guide/vcl-example-websockets.html)
+- [Apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html)
+- [NGINX](https://www.nginx.com/blog/websocket-nginx/)
+- [HAProxy](http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/)
+- [Varnish](https://www.varnish-cache.org/docs/4.1/users-guide/vcl-example-websockets.html)
Workhorse won't let WebSocket requests through to non-WebSocket endpoints, so
it's safe to enable support for these headers globally. If you'd rather had a
@@ -60,8 +73,8 @@ the `Connection` and `Upgrade` hop-by-hop headers in the *first* HTTP reverse
proxy in the chain. For most users, this will be the NGINX server bundled with
Omnibus GitLab, in which case, you need to:
-* Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
-* Ensure the whole block is uncommented, and then comment out or remove the
+- Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
+- Ensure the whole block is uncommented, and then comment out or remove the
`Connection` and `Upgrade` lines.
For your own load balancer, just reverse the configuration changes recommended
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index 0ca1d77f1d0..d8f80965c21 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -52,9 +52,9 @@ and these checks will verify them against current files.
Currently, integrity checks are supported for the following types of file:
-* CI artifacts (Available from version 10.7.0)
-* LFS objects (Available from version 10.6.0)
-* User uploads (Available from version 10.6.0)
+- CI artifacts (Available from version 10.7.0)
+- LFS objects (Available from version 10.6.0)
+- User uploads (Available from version 10.6.0)
**Omnibus Installation**
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 12238ba7b32..51e1518d73f 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -7,8 +7,8 @@
Legacy Storage is the storage behavior prior to version 10.0. For historical
reasons, GitLab replicated the same mapping structure from the projects URLs:
-* Project's repository: `#{namespace}/#{project_name}.git`
-* Project's wiki: `#{namespace}/#{project_name}.wiki.git`
+- Project's repository: `#{namespace}/#{project_name}.git`
+- Project's wiki: `#{namespace}/#{project_name}.wiki.git`
This structure made it simple to migrate from existing solutions to GitLab and
easy for Administrators to find where the repository is stored.
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 643c5b9fe80..bd702dcc9ec 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -211,5 +211,5 @@ The output in `/tmp/unicorn.txt` may help diagnose the root cause.
# More information
-* [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
-* [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
+- [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
+- [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
diff --git a/doc/api/README.md b/doc/api/README.md
index d481d0699e7..3ed1a3799c8 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -49,6 +49,7 @@ The following API resources are available:
- [Projects](projects.md) including setting Webhooks
- [Project access requests](access_requests.md)
- [Project badges](project_badges.md)
+ - [Project clusters](project_clusters.md)
- [Project-level variables](project_level_variables.md)
- [Project import/export](project_import_export.md)
- [Project members](members.md)
diff --git a/doc/api/lint.md b/doc/api/lint.md
index c37a8bff749..a0307f7081d 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -20,7 +20,7 @@ Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very pic
Example responses:
-* Valid content:
+- Valid content:
```json
{
@@ -29,7 +29,7 @@ Example responses:
}
```
-* Invalid content:
+- Invalid content:
```json
{
@@ -40,7 +40,7 @@ Example responses:
}
```
-* Without the content attribute:
+- Without the content attribute:
```json
{
@@ -49,4 +49,3 @@ Example responses:
```
[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
-
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 8a1e6b52092..6786c0c5b5c 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,33 +1,33 @@
# GitLab as an OAuth2 provider
-This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access GitLab resources on user's behalf.
+This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access GitLab resources on user's behalf.
If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [OAuth2 provider](../integration/oauth_provider.md)
documentation.
-This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
+This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
## Supported OAuth2 Flows
-GitLab currently supports following authorization flows:
+GitLab currently supports following authorization flows:
-* *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
-* *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
-* *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
+- *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
+- *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
+- *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
Please refer to [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out in details how all those flows work and pick the right one for your use case.
-Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
-in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
+Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
+in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
you'll obtain `application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
->**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
-value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
+>**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
+value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
have been a requirement in the standard!
-In the following sections you will find detailed instructions on how to obtain authorization with each flow.
+In the following sections you will find detailed instructions on how to obtain authorization with each flow.
-### Web Application Flow
+### Web Application Flow
Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1) for a detailed flow description
@@ -48,7 +48,7 @@ You should then use the `code` to request an access token.
#### 2. Requesting access token
-Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
+Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
we are using Ruby's `rest-client`:
```
@@ -73,13 +73,13 @@ You can now make requests to the API with the access token returned.
Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.2) for a detailed flow description.
-Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
-or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
+or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+
+>**Important:** Avoid using this flow for applications that store data outside of the GitLab instance. If you do, make sure to verify `application id`
+associated with access token before granting access to the data
+(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
->**Important:** Avoid using this flow for applications that store data outside of the GitLab instance. If you do, make sure to verify `application id`
-associated with access token before granting access to the data
-(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
-
#### 1. Requesting access token
@@ -89,7 +89,7 @@ To request the access token, you should redirect the user to the `/oauth/authori
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
```
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
+This will ask the user to approve the application's access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
will include a fragment with `access_token` as well as token details in GET parameters, for example:
```
@@ -100,7 +100,7 @@ http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE
Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.3) for a detailed flow description.
-> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
+> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
turned on. These users can access the API using [personal access tokens] instead.
In this flow, a token is requested in exchange for the resource owner credentials (username and password).
@@ -109,7 +109,7 @@ client is part of the device operating system or a highly privileged application
available (such as an authorization code).
>**Important:**
-Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
+Never store the user's credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
are a better choice.
Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
@@ -148,7 +148,7 @@ puts access_token.token
## Access GitLab API with `access token`
-The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
+The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
```
GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
```
@@ -160,4 +160,4 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api
```
[personal access tokens]: ../user/profile/personal_access_tokens.md
-[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow \ No newline at end of file
+[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
new file mode 100644
index 00000000000..c51a3564211
--- /dev/null
+++ b/doc/api/project_clusters.md
@@ -0,0 +1,346 @@
+# Project clusters API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23922)
+in GitLab 11.7.
+
+NOTE: **Note:**
+User will need at least maintainer access to use these endpoints.
+
+## List project clusters
+
+Returns a list of project clusters.
+
+```
+GET /projects/:id/clusters
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters
+```
+
+Example response:
+
+```json
+[
+ {
+ "id":18,
+ "name":"cluster-1",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "namespace":"cluster-1-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ }
+ },
+ {
+ "id":19,
+ "name":"cluster-2",
+ ...
+ }
+]
+```
+
+## Get a single project cluster
+
+Gets a single project cluster.
+
+```bash
+GET /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/18
+```
+
+Example response:
+
+```json
+{
+ "id":18,
+ "name":"cluster-1",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "namespace":"cluster-1-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+```
+
+## Add existing cluster to project
+
+Adds an existing Kubernetes cluster to the project.
+
+```bash
+POST /projects/:id/clusters/user
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `name` | String | yes | The name of the cluster |
+| `enabled` | Boolean | no | Determines if cluster is active or not, 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`. |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/user \
+-H "Accept: application/json" \
+-H "Content-Type:application/json" \
+-X POST --data '{"name":"cluster-5", "platform_kubernetes_attributes":{"api_url":"https://35.111.51.20","token":"12345","namespace":"cluster-5-namespace","ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"}}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"cluster-5",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://35.111.51.20",
+ "namespace":"cluster-5-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh:://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+```
+
+## Edit project cluster
+
+Updates an existing project cluster.
+
+```bash
+PUT /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `name` | String | no | The name 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 |
+
+NOTE: **Note:**
+`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
+through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing cluster to project"](#add-existing-cluster-to-project) endpoint.
+
+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"}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"new-cluster-name",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://new-api-url.com",
+ "namespace":"cluster-5-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":null
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh:://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+
+```
+
+## Delete project cluster
+
+Deletes an existing project cluster.
+
+```
+DELETE /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/23'
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 465b1494b2a..538cd34de43 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -7,30 +7,29 @@ This is determined by the `visibility` field in the project.
Values for the project visibility level are:
-* `private`:
+- `private`:
Project access must be granted explicitly for each user.
-* `internal`:
+- `internal`:
The project can be cloned by any logged in user.
-* `public`:
+- `public`:
The project can be cloned without any authentication.
## Project merge method
There are currently three options for `merge_method` to choose from:
-* `merge`:
+- `merge`:
A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
-* `rebase_merge`:
+- `rebase_merge`:
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.
-* `ff`:
+- `ff`:
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.
-
## List all projects
Get a list of all visible projects across GitLab for the authenticated user.
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 877a7af3149..9f552a10589 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -212,7 +212,7 @@ Response:
## Merge Base
-Get the common ancestor for 2 refs (commit SHAs, branch names or tags).
+Get the common ancestor for 2 or more refs (commit SHAs, branch names or tags).
```
GET /projects/:id/repository/merge_base
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 315d0c5e7ef..010c579b83e 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -419,7 +419,7 @@ and/or `production`) you can see this information in the merge request itself.
> Introduced in GitLab 8.17. In GitLab 11.5 the file links
are surfaced to the merge request widget.
-You can specify a Route Map to get GitLab to show "View on <environment URL>"
+You can specify a Route Map to get GitLab to show **View on ...**
buttons to go directly from a file to that file's representation on the
[deployed website via Review Apps](review_apps/index.md).
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index d299e28d2e6..0cf9daed22f 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -3,7 +3,10 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
Interactive web terminals give the user access to a terminal in GitLab for
-running one-off commands for their CI pipeline.
+running one-off commands for their CI pipeline. Since this is giving the user
+shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
+is deployed, some [security precautions](../../administration/integration/terminal.md#security) were
+taken to protect the users.
NOTE: **Note:**
GitLab.com does not support interactive web terminal at the moment – neither
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index f8cf9de9aff..d2a00b9218d 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -241,9 +241,9 @@ 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:
-* A (1, 3)
-* B (2, 4)
-* C (6, 7)
+- A (1, 3)
+- B (2, 4)
+- C (6, 7)
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:
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index ed0adc5414b..25d189afb24 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -250,6 +250,23 @@ 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
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
+
+You can configure [Auto DevOps](../../topics/autodevops/index.md) to
+pass CI variables to the running application by prefixing the key of the
+variable with `K8S_SECRET_`.
+
+These [prefixed
+variables](../../topics/autodevops/index.md#application-secret-variables) will
+then be available as environment variables on the running application
+container.
+
+CAUTION: **Caution:**
+Variables with multiline values are not currently supported due to
+limitations with the current Auto DevOps scripting environment.
+
## Debug tracing
> Introduced in GitLab Runner 1.7.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 98542c7e82c..efee2852eb8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -332,10 +332,10 @@ jobs are created:
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
+- `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 to specify a repository path to filter jobs for
+- `only` and `except` allow the use of regular expressions (using [Ruby regexp syntax](https://ruby-doc.org/core/Regexp.html)).
+- `only` and `except` allow to specify a repository path to filter jobs for
forks.
In addition, `only` and `except` allow the use of special keywords:
diff --git a/doc/development/adding_database_indexes.md b/doc/development/adding_database_indexes.md
index d1d2b8c4907..c47ce0f1182 100644
--- a/doc/development/adding_database_indexes.md
+++ b/doc/development/adding_database_indexes.md
@@ -113,9 +113,9 @@ the meaning of the various columns can be found at
Because the output of this query relies on the actual usage of your database it
may be affected by factors such as (but not limited to):
-* Certain queries never being executed, thus not being able to use certain
+- Certain queries never being executed, thus not being able to use certain
indexes.
-* Certain tables having little data, resulting in PostgreSQL using sequence
+- Certain tables having little data, resulting in PostgreSQL using sequence
scans instead of index scans.
In other words, this data is only reliable for a frequently used database with
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index dd4a9e058d7..642dac614c7 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -25,9 +25,9 @@ should only be used for data migrations.
Some examples where background migrations can be useful:
-* Migrating events from one table to multiple separate tables.
-* Populating one column based on JSON stored in another column.
-* Migrating data that depends on the output of external services (e.g. an API).
+- Migrating events from one table to multiple separate tables.
+- Populating one column based on JSON stored in another column.
+- Migrating data that depends on the output of external services (e.g. an API).
## Isolation
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 9dffca5b99c..7ac846e4c4d 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -97,28 +97,28 @@ When your code contains more than 500 changes, any major breaking changes, or an
This [documentation](issue_workflow.md) outlines the current issue process.
-* [Type labels](issue_workflow.md#type-labels)
-* [Subject labels](issue_workflow.md#subject-labels)
-* [Team labels](issue_workflow.md#team-labels)
-* [Release Scoping labels](issue_workflow.md#release-scoping-labels)
-* [Priority labels](issue_workflow.md#priority-labels)
-* [Severity labels](issue_workflow.md#severity-labels)
-* [Label for community contributors](issue_workflow.md#label-for-community-contributors)
-* [Issue triaging](issue_workflow.md#issue-triaging)
-* [Feature proposals](issue_workflow.md#feature-proposals)
-* [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
-* [Issue weight](issue_workflow.md#issue-weight)
-* [Regression issues](issue_workflow.md#regression-issues)
-* [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
-* [Stewardship](issue_workflow.md#stewardship)
+- [Type labels](issue_workflow.md#type-labels)
+- [Subject labels](issue_workflow.md#subject-labels)
+- [Team labels](issue_workflow.md#team-labels)
+- [Release Scoping labels](issue_workflow.md#release-scoping-labels)
+- [Priority labels](issue_workflow.md#priority-labels)
+- [Severity labels](issue_workflow.md#severity-labels)
+- [Label for community contributors](issue_workflow.md#label-for-community-contributors)
+- [Issue triaging](issue_workflow.md#issue-triaging)
+- [Feature proposals](issue_workflow.md#feature-proposals)
+- [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
+- [Issue weight](issue_workflow.md#issue-weight)
+- [Regression issues](issue_workflow.md#regression-issues)
+- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
+- [Stewardship](issue_workflow.md#stewardship)
## Merge requests
This [documentation](merge_request_workflow.md) outlines the current merge request process.
-* [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)
+- [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)
## Style guides
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index a18c21d921a..590dc41cf98 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -34,7 +34,7 @@ in October, 2018.
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 [GiLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
+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).
diff --git a/doc/development/emails.md b/doc/development/emails.md
index 35ada35babe..8baf343b133 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -76,14 +76,32 @@ See the [Rails guides] for more info.
## Email namespace
-If you need to implement a new feature which requires a new email handler, follow these rules:
+As of GitLab 11.7, we support a new format for email handler addresses. This was done to
+support catch-all mailboxes.
- - You must choose a namespace. The namespace cannot contain `/` or `+`, and cannot match `\h{16}`.
- - If your feature is related to a project, you will append the namespace **after** the project path, separated by a `+`
- - If you have different actions in the namespace, you add the actions **after** the namespace separated by a `+`. The action name cannot contain `/` or `+`, , and cannot match `\h{16}`.
- - You will register your handlers in `lib/gitlab/email/handler.rb`
+If you need to implement a feature which requires a new email handler, follow these rules
+for the format of the email key:
-Therefore, these are the only valid formats for an email handler:
+- Actions are always at the end, separated by `-`. For example `-issue` or `-merge-request`
+- If your feature is related to a project, the key begins with the project identifiers (project path slug
+ and project id), separated by `-`. For example, `gitlab-org-gitlab-ce-20`
+- Additional information, such as an author's token, can be added between the project identifiers and
+ the action, separated by `-`. For example, `gitlab-org-gitlab-ce-20-Author_Token12345678-issue`
+- You register your handlers in `lib/gitlab/email/handler.rb`
+
+Examples of valid email keys:
+
+ - `gitlab-org-gitlab-ce-20-Author_Token12345678-issue` (create a new issue)
+ - `gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request` (create a new merge request)
+ - `1234567890abcdef1234567890abcdef-unsubscribe` (unsubscribe from a conversation)
+ - `1234567890abcdef1234567890abcdef` (reply to a conversation)
+
+Please note that the action `-issue-` is used in GitLab Premium as the handler for the Service Desk feature.
+
+### Legacy format
+
+Although we continue to support the older legacy format, no new features should use a legacy format.
+These are the only valid legacy formats for an email handler:
- `path/to/project+namespace`
- `path/to/project+namespace+action`
diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md
index 0e9126ee667..52462a4bec9 100644
--- a/doc/development/fe_guide/components.md
+++ b/doc/development/fe_guide/components.md
@@ -1,18 +1,19 @@
# Components
## Contents
-* [Dropdowns](#dropdowns)
-* [Modals](#modals)
+
+- [Dropdowns](#dropdowns)
+- [Modals](#modals)
## Dropdowns
See also the [corresponding UX guide](https://design.gitlab.com/#/components/dropdowns).
### How to style a bootstrap dropdown
+
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
-
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index ce96a9fc8ae..e6aa0be671f 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -177,28 +177,28 @@ droplab.init().addData('trigger', [{
DropLab adds some CSS classes to help lower the barrier to integration.
-For example,
+For example:
-* The `droplab-item-selected` css class is added to items that have been selected
-either by a mouse click or by enter key selection.
-* The `droplab-item-active` css class is added to items that have been selected
-using arrow key navigation.
-* You can add the `droplab-item-ignore` css class to any item that you do not want to be selectable. For example,
-an `<li class="divider"></li>` list divider element that should not be interactive.
+- The `droplab-item-selected` css class is added to items that have been selected
+ either by a mouse click or by enter key selection.
+- The `droplab-item-active` css class is added to items that have been selected
+ using arrow key navigation.
+- You can add the `droplab-item-ignore` css class to any item that you do not want to be selectable. For example,
+ an `<li class="divider"></li>` list divider element that should not be interactive.
## Internal events
DropLab uses some custom events to help lower the barrier to integration.
-For example,
+For example:
-* The `click.dl` event is fired when an `li` list item has been clicked. It is also
-fired when a list item has been selected with the keyboard. It is also fired when a
-`HookButton` button is clicked (a registered `button` tag or `a` tag trigger).
-* The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event.
-* The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event.
-* The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event.
-* The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event.
+- The `click.dl` event is fired when an `li` list item has been clicked. It is also
+ fired when a list item has been selected with the keyboard. It is also fired when a
+ `HookButton` button is clicked (a registered `button` tag or `a` tag trigger).
+- The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event.
+- The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event.
+- The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event.
+- The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event.
These custom events add a `detail` object to the vanilla `Event` object that provides some potentially useful data.
@@ -233,9 +233,9 @@ droplab.init(trigger, list, [droplabAjax], {
### Documentation
-* [Ajax plugin](plugins/ajax.md)
-* [Filter plugin](plugins/filter.md)
-* [InputSetter plugin](plugins/input_setter.md)
+- [Ajax plugin](plugins/ajax.md)
+- [Filter plugin](plugins/filter.md)
+- [InputSetter plugin](plugins/input_setter.md)
### Development
diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md
index 9c7e56fa448..b6a883ce6c4 100644
--- a/doc/development/fe_guide/droplab/plugins/ajax.md
+++ b/doc/development/fe_guide/droplab/plugins/ajax.md
@@ -8,10 +8,10 @@ Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `Dro
`Ajax` requires 2 config values, the `endpoint` and `method`.
-* `endpoint` should be a URL to the request endpoint.
-* `method` should be `setData` or `addData`.
-* `setData` completely replaces the dropdown with the response data.
-* `addData` appends the response data to the current dropdown list.
+- `endpoint` should be a URL to the request endpoint.
+- `method` should be `setData` or `addData`.
+- `setData` completely replaces the dropdown with the response data.
+- `addData` appends the response data to the current dropdown list.
```html
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index 0853ea4d320..ddc6a3386c7 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -7,8 +7,8 @@ to the dropdown using a simple fuzzy string search of an input value.
Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
-* `Filter` requires a config value for `template`.
-* `template` should be the key of the objects within your data array that you want to compare
+- `Filter` requires a config value for `template`.
+- `template` should be the key of the objects within your data array that you want to compare
to the user input string, for filtering.
```html
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index 94f3c8254a8..8e28a41f32e 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -6,9 +6,9 @@
Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
-* `InputSetter` requires a config value for `input` and `valueAttribute`.
-* `input` should be the DOM element that you want to manipulate.
-* `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
+- `InputSetter` requires a config value for `input` and `valueAttribute`.
+- `input` should be the DOM element that you want to manipulate.
+- `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
to update the `input` element with.
You can also set the `InputSetter` config to an array of objects, which will allow you to update multiple elements.
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index cca3ad6fae6..ad87ecf1b87 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -118,7 +118,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
## [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)
+- [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/performance.md b/doc/development/fe_guide/performance.md
index ef0eed786d2..e5a383c25f5 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -29,8 +29,8 @@ To improve the time to first render we are using lazy loading for images. This w
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
-* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`
-* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
+- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 6e014e8c751..b90dc90e424 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -4,15 +4,15 @@ We use the [CarrierWave] gem to handle file upload, store and retrieval.
There are many places where file uploading is used, according to contexts:
-* System
+- System
- Instance Logo (logo visible in sign in/sign up pages)
- Header Logo (one displayed in the navigation bar)
-* Group
+- Group
- Group avatars
-* User
+- User
- User avatars
- User snippet attachments
-* Project
+- Project
- Project avatars
- Issues/MR/Notes Markdown attachments
- Issues/MR/Notes Legacy Markdown attachments
@@ -52,7 +52,7 @@ hash of the project ID instead, if project migrates to the new approach (introdu
### Path segments
-Files are stored at multiple locations and use different path schemes.
+Files are stored at multiple locations and use different path schemes.
All the `GitlabUploader` derived classes should comply with this path segment schema:
```
@@ -61,7 +61,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<gitlab_root>/public/` | `uploads/-/system/` | `user/avatar/:id/` | `:filename` |
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | `CarrierWave::Uploader#store_dir` | |
+| | `CarrierWave::Uploader#store_dir` | |
| FileUploader
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
@@ -69,7 +69,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<gitlab_root>/shared/` | `snippets/` | `:secret/` | `:filename` |
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | `CarrierWave::Uploader#store_dir` | |
+| | `CarrierWave::Uploader#store_dir` | |
| | | `FileUploader#upload_path |
| ObjectStore::Concern (store = remote)
@@ -77,7 +77,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<bucket_name>` | <ignored> | `user/avatar/:id/` | `:filename` |
| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- |
| `#fog_dir` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | | `ObjectStorage::Concern#store_dir` | |
+| | | `ObjectStorage::Concern#store_dir` | |
| | | `ObjectStorage::Concern#upload_path |
```
diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md
index 863ac049db6..5445f36b9fa 100644
--- a/doc/development/github_importer.md
+++ b/doc/development/github_importer.md
@@ -13,20 +13,20 @@ or Rake tasks. The parallel importer on the other hand uses Sidekiq.
## Requirements
-* GitLab CE 10.2.0 or newer.
-* Sidekiq workers that process the `github_importer` and
+- GitLab CE 10.2.0 or newer.
+- Sidekiq workers that process the `github_importer` and
`github_importer_advance_stage` queues (this is enabled by default).
-* Octokit (used for interacting with the GitHub API)
+- Octokit (used for interacting with the GitHub API).
## Code structure
The importer's codebase is broken up into the following directories:
-* `lib/gitlab/github_import`: this directory contains most of the code such as
+- `lib/gitlab/github_import`: this directory contains most of the code such as
the classes used for importing resources.
-* `app/workers/gitlab/github_import`: this directory contains the Sidekiq
+- `app/workers/gitlab/github_import`: this directory contains the Sidekiq
workers.
-* `app/workers/concerns/gitlab/github_import`: this directory contains a few
+- `app/workers/concerns/gitlab/github_import`: this directory contains a few
modules reused by the various Sidekiq workers.
## Architecture overview
@@ -188,8 +188,8 @@ projects get imported the fewer GitHub API calls will be needed.
The code for this resides in:
-* `lib/gitlab/github_import/user_finder.rb`
-* `lib/gitlab/github_import/caching.rb`
+- `lib/gitlab/github_import/user_finder.rb`
+- `lib/gitlab/github_import/caching.rb`
## Mapping labels and milestones
@@ -204,6 +204,6 @@ project that is being imported.
The code for this resides in:
-* `lib/gitlab/github_import/label_finder.rb`
-* `lib/gitlab/github_import/milestone_finder.rb`
-* `lib/gitlab/github_import/caching.rb`
+- `lib/gitlab/github_import/label_finder.rb`
+- `lib/gitlab/github_import/milestone_finder.rb`
+- `lib/gitlab/github_import/caching.rb`
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index a5a34d82bec..1b9ebb50c29 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -96,8 +96,8 @@ end
### Why
-* Because it is not isolated therefore it might be broken at times.
-* Because it doesn't work whenever the method we want to stub was defined
+- Because it is not isolated therefore it might be broken at times.
+- Because it doesn't work whenever the method we want to stub was defined
in a prepended module, which is very likely the case in EE. We could see
error like this:
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index a36dc6424a7..5f95cf3707c 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -11,12 +11,12 @@ Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation`
module. This module offers a few different methods that can be used to
instrument code:
-* `instrument_method`: instruments a single class method.
-* `instrument_instance_method`: instruments a single instance method.
-* `instrument_class_hierarchy`: given a Class this method will recursively
+- `instrument_method`: instruments a single class method.
+- `instrument_instance_method`: instruments a single instance method.
+- `instrument_class_hierarchy`: given a Class this method will recursively
instrument all sub-classes (both class and instance methods).
-* `instrument_methods`: instruments all public and private class methods of a Module.
-* `instrument_instance_methods`: instruments all public and private instance methods of a
+- `instrument_methods`: instruments all public and private class methods of a Module.
+- `instrument_instance_methods`: instruments all public and private instance methods of a
Module.
To remove the need for typing the full `Gitlab::Metrics::Instrumentation`
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
index ee01c89e0ed..6f1baad3a2d 100644
--- a/doc/development/merge_request_performance_guidelines.md
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -9,8 +9,8 @@ To measure the impact of a merge request you can use
[Sherlock](profiling.md#sherlock). It's also highly recommended that you read
the following guides:
-* [Performance Guidelines](performance.md)
-* [What requires downtime?](what_requires_downtime.md)
+- [Performance Guidelines](performance.md)
+- [What requires downtime?](what_requires_downtime.md)
## Impact Analysis
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 23aa318ef91..98b54684d39 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -40,7 +40,7 @@ migrations _unless_ schema changes are absolutely required to solve a problem.
## What Requires Downtime?
The document ["What Requires Downtime?"](what_requires_downtime.md) specifies
-various database operations, such as
+various database operations, such as
- [adding, dropping, and renaming columns](what_requires_downtime.md#adding-columns)
- [changing column constraints and types](what_requires_downtime.md#changing-column-constraints)
@@ -59,9 +59,9 @@ the migrations that _do_ require downtime.
To tag a migration, add the following two constants to the migration class'
body:
-* `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
+- `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
downtime.
-* `DOWNTIME_REASON`: a String containing the reason for the migration requiring
+- `DOWNTIME_REASON`: a String containing the reason for the migration requiring
downtime. This constant **must** be set when `DOWNTIME` is set to `true`.
For example:
@@ -318,8 +318,8 @@ end
Instead of using these methods one should use the following methods to store timestamps with timezones:
-* `add_timestamps_with_timezone`
-* `timestamps_with_timezone`
+- `add_timestamps_with_timezone`
+- `timestamps_with_timezone`
This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't
suddenly use a different timezone when the system's timezone changes. It also makes it very clear which
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
index 899efb398cd..8ae58d30c35 100644
--- a/doc/development/new_fe_guide/development/components.md
+++ b/doc/development/new_fe_guide/development/components.md
@@ -6,16 +6,16 @@ We have a lot of graphing libraries in our codebase to render graphs. In an effo
We chose D3 as our library going forward because of the following features:
-* [Tree shaking webpack capabilities.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
-* [Compatible with vue.js as well as vanilla javascript.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
+- [Tree shaking webpack capabilities](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40).
+- [Compatible with vue.js as well as vanilla javascript](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40).
D3 is very popular across many projects outside of GitLab:
-* [The New York Times](https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html)
-* [plot.ly](https://plot.ly/)
-* [Droptask](https://www.droptask.com/)
+- [The New York Times](https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html)
+- [plot.ly](https://plot.ly/)
+- [Droptask](https://www.droptask.com/)
Within GitLab, D3 has been used for the following notable features
-* [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
-* Contribution calendars
+- [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
+- Contribution calendars
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
index 5ccd5357314..640a8d64176 100644
--- a/doc/development/new_fe_guide/development/performance.md
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -7,10 +7,10 @@ We have a performance dashboard available in one of our [grafana instances](http
These pages can be found inside a text file in the gitlab-build-images [repository](https://gitlab.com/gitlab-org/gitlab-build-images) called [gitlab.txt](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/scripts/gitlab.txt)
Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/team) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
-There are 3 recommended high impact metrics to review on each page
+There are 3 recommended high impact metrics to review on each page:
-* [First visual change](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)
-* [Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
-* [Visual Complete 95%](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+- [First visual change](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)
+- [Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+- [Visual Complete 95%](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
For these metrics, lower numbers are better as it means that the website is more performant.
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index 0d98180add0..f5dfb1a31e1 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -1,29 +1,27 @@
# Overview of Frontend Testing
-Tests relevant for frontend development can be found at two places:
+Tests relevant for frontend development can be found at the following places:
-- `spec/javascripts/` which are run by Karma and contain
+- `spec/javascripts/` which are run by Karma (command: `yarn karma`) and contain
- [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests)
-- `spec/frontend/` which are run by Jest and contain
+- `spec/frontend/` which are run by Jest (command: `yarn jest`) and contain
- [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests)
- `spec/features/` which are run by RSpec and contain
- [feature tests](#feature-tests)
-All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#53757]).
+All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#52483](https://gitlab.com/gitlab-org/gitlab-ce/issues/52483)).
In addition there were feature tests in `features/` run by Spinach in the past.
These have been removed from our codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)).
See also:
-- [old testing guide](../../testing_guide/frontend_testing.html)
-- [notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components)
-
-[#53757]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53757
+- [Old testing guide](../../testing_guide/frontend_testing.html).
+- [Notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components).
## Frontend unit tests
@@ -246,6 +244,8 @@ Their abstraction level is comparable to how a user would interact with the UI.
In contrast to [frontend integration tests](#frontend-integration-tests), feature tests make requests against the real backend instead of using fixtures.
This also implies that database queries are executed which makes this category significantly slower.
+See also the [RSpec testing guidelines](../../testing_guide/best_practices.md#rspec).
+
### When to use feature tests
- use cases that require a backend and cannot be tested using fixtures
@@ -302,8 +302,8 @@ Check an example in [spec/javascripts/ide/stores/actions_spec.jsspec/javascripts
To make mounting a Vue component easier and more readable, we have a few helpers available in `spec/helpers/vue_mount_component_helper`.
-* `createComponentWithStore`
-* `mountComponentWithStore`
+- `createComponentWithStore`
+- `mountComponentWithStore`
Examples of usage:
diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/new_fe_guide/modules/index.md
index 0a7f2dbd819..a7820442df0 100644
--- a/doc/development/new_fe_guide/modules/index.md
+++ b/doc/development/new_fe_guide/modules/index.md
@@ -1,5 +1,5 @@
# Modules
-* [DirtySubmit](dirty_submit.md)
+- [DirtySubmit](dirty_submit.md)
- Disable form submits until there are unsaved changes. \ No newline at end of file
+ Disable form submits until there are unsaved changes.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 4cc2fdc9a58..972c93be817 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -23,9 +23,9 @@ The process of solving performance problems is roughly as follows:
When providing timings make sure to provide:
-* The 95th percentile
-* The 99th percentile
-* The mean
+- The 95th percentile
+- The 99th percentile
+- The mean
When providing screenshots of graphs, make sure that both the X and Y axes and
the legend are clearly visible. If you happen to have access to GitLab.com's own
@@ -36,12 +36,12 @@ graphs/dashboards.
GitLab provides built-in tools to help improve performance and availability:
-* [Profiling](profiling.md)
- * [Sherlock](profiling.md#sherlock)
-* [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
-* [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability.
+- [Profiling](profiling.md).
+ - [Sherlock](profiling.md#sherlock).
+- [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.
+- [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability.
GitLab employees can use GitLab.com's performance monitoring systems located at
<https://dashboards.gitlab.net>, this requires you to log in using your
@@ -269,11 +269,11 @@ piece of code is worth optimizing. The only two things you can do are:
Some examples of changes that aren't really important/worth the effort:
-* Replacing double quotes with single quotes.
-* Replacing usage of Array with Set when the list of values is very small.
-* Replacing library A with library B when both only take up 0.1% of the total
+- Replacing double quotes with single quotes.
+- Replacing usage of Array with Set when the list of values is very small.
+- Replacing library A with library B when both only take up 0.1% of the total
execution time.
-* Calling `freeze` on every string (see [String Freezing](#string-freezing)).
+- Calling `freeze` on every string (see [String Freezing](#string-freezing)).
## Slow Operations & Sidekiq
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 62141356f59..97424d90fb5 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -13,7 +13,7 @@ Permissions are broken into two parts: `conditions` and `rules`. Conditions are
Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class.
-``` ruby
+```ruby
class FooPolicy < BasePolicy
condition(:is_public) do
# @subject guaranteed to be an instance of Foo
@@ -37,7 +37,7 @@ Conditions are cached according to their scope. Scope and ordering will be cover
A `rule` is a logical combination of conditions and other rules, that are configured to enable or prevent certain abilities. It is important to note that the rule configuration is static - a rule's logic cannot touch the database or know about `@user` or `@subject`. This allows us to cache only at the condition level. Rules are specified through the `rule` method, which takes a block of DSL configuration, and returns an object that responds to `#enable` or `#prevent`:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
# ...
@@ -57,10 +57,10 @@ end
Within the rule DSL, you can use:
-* A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
-* `~` indicates negation
-* `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`
-* `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
+- A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
+- `~` indicates negation.
+- `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`.
+- `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
## Scores, Order, Performance
@@ -72,7 +72,7 @@ When a policy is asked whether a particular ability is allowed (`policy.allowed?
Sometimes, a condition will only use data from `@user` or only from `@subject`. In this case, we want to change the scope of the caching, so that we don't recalculate conditions unnecessarily. For example, given:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
condition(:expensive_condition) { @subject.expensive_query? }
@@ -82,7 +82,7 @@ end
Naively, if we call `Ability.can?(user1, :some_ability, foo)` and `Ability.can?(user2, :some_ability, foo)`, we would have to calculate the condition twice - since they are for different users. But if we use the `scope: :subject` option:
-``` ruby
+```ruby
condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }
```
@@ -93,7 +93,7 @@ both user and subject (including a simple anonymous check!) your result will be
Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
-``` ruby
+```ruby
def users_that_can_read_project(users, project)
DeclarativePolicy.subject_scope do
users.select { |u| allowed?(u, :read_project, project) }
@@ -105,9 +105,9 @@ This will, for example, prefer checking `project.public?` to checking `user.admi
## Delegation
-Delegation is the inclusion of rules from another policy, on a different subject. For example,
+Delegation is the inclusion of rules from another policy, on a different subject. For example:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
delegate { @subject.project }
end
diff --git a/doc/development/polymorphic_associations.md b/doc/development/polymorphic_associations.md
index d63b9fb115f..5d69c8add38 100644
--- a/doc/development/polymorphic_associations.md
+++ b/doc/development/polymorphic_associations.md
@@ -7,9 +7,9 @@ usually works by adding two columns to a table: a target type column, and a
target id. For example, at the time of writing we have such a setup for
`members` with the following columns:
-* `source_type`: a string defining the model to use, can be either `Project` or
+- `source_type`: a string defining the model to use, can be either `Project` or
`Namespace`.
-* `source_id`: the ID of the row to retrieve based on `source_type`. For
+- `source_id`: the ID of the row to retrieve based on `source_type`. For
example, when `source_type` is `Project` then `source_id` will contain a
project ID.
@@ -92,10 +92,10 @@ AND source_id = 4
Instead such a table should be broken up into separate tables. For example, you
may end up with 4 tables in this case:
-* project_members
-* group_members
-* pending_project_members
-* pending_group_members
+- project_members
+- group_members
+- pending_project_members
+- pending_group_members
This makes querying data trivial. For example, to get the members of a group
you'd run:
diff --git a/doc/development/post_deployment_migrations.md b/doc/development/post_deployment_migrations.md
index 5986efa9974..a41096aa3eb 100644
--- a/doc/development/post_deployment_migrations.md
+++ b/doc/development/post_deployment_migrations.md
@@ -70,6 +70,6 @@ version (which doesn't depend on the column anymore) has been deployed.
Some other examples where these migrations are useful:
-* Cleaning up data generated due to a bug in GitLab
-* Removing tables
-* Migrating jobs from one Sidekiq queue to another
+- Cleaning up data generated due to a bug in GitLab
+- Removing tables
+- Migrating jobs from one Sidekiq queue to another
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 2ad748d4802..ae9bf863419 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -38,6 +38,14 @@ Note that since you can't see the questions from stdout, you might just want
to `echo 'yes'` to keep it running. It would still print the errors on stderr
so no worries about missing errors.
+### Extra Project seed options
+
+There are a few environment flags you can pass to change how projects are seeded
+
+- `SIZE`: defaults to `8`, max: `32`. Amount of projects to create.
+- `LARGE_PROJECTS`: defaults to false. If set will clone 6 large projects to help with testing.
+- `FORK`: defaults to false. If set to `true` will fork `torvalds/linux` five times. Can also be set to an existing project full_path and it will fork that instead.
+
### Notes for MySQL
Since the seeds would contain various UTF-8 characters, such as emojis or so,
diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md
index 83d7d42bd1f..01cedf734fb 100644
--- a/doc/development/reusing_abstractions.md
+++ b/doc/development/reusing_abstractions.md
@@ -149,11 +149,11 @@ typically in JSON.
These are class methods defined by _GitLab itself_, including the following
methods provided by Active Record:
-* `find`
-* `find_by_id`
-* `delete_all`
-* `destroy`
-* `destroy_all`
+- `find`
+- `find_by_id`
+- `delete_all`
+- `destroy`
+- `destroy_all`
Any other methods such as `find_by(some_column: X)` are not included, and
instead fall under the "Active Record" abstraction.
@@ -163,10 +163,10 @@ instead fall under the "Active Record" abstraction.
Instance methods defined on Active Record models by _GitLab itself_. Methods
provided by Active Record are not included, except for the following methods:
-* `save`
-* `update`
-* `destroy`
-* `delete`
+- `save`
+- `update`
+- `destroy`
+- `delete`
### Active Record
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index b65fbc9d958..ef1aba95712 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -10,12 +10,12 @@ disable those changes, without having to revert an entire release.
Starting with GitLab 11.4, developers are required to use feature flags for
non-trivial changes. Such changes include:
-* New features (e.g. a new merge request widget, epics, etc).
-* Complex performance improvements that may require additional testing in
+- New features (e.g. a new merge request widget, epics, etc).
+- Complex performance improvements that may require additional testing in
production, such as rewriting complex queries.
-* Invasive changes to the user interface, such as a new navigation bar or the
+- Invasive changes to the user interface, such as a new navigation bar or the
removal of a sidebar.
-* Adding support for importing projects from a third-party service.
+- Adding support for importing projects from a third-party service.
In all cases, those working on the changes can best decide if a feature flag is
necessary. For example, changing the color of a button doesn't need a feature
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index f8993653aec..6012f1080ab 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -185,8 +185,8 @@ See this [section][vue-test].
`rake karma` runs the frontend-only (JavaScript) tests.
It consists of two subtasks:
-* `rake karma:fixtures` (re-)generates fixtures
-* `rake karma:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates fixtures
+- `rake karma:tests` actually executes the tests
As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`)
is sufficient (and saves you some time).
@@ -243,14 +243,14 @@ 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:
-* Array.from
-* Array.first
-* Async functions
-* Generators
-* Array destructuring
-* For..Of
-* Symbol/Symbol.iterator
-* Spread
+- Array.from
+- Array.first
+- Async functions
+- Generators
+- Array destructuring
+- For..Of
+- Symbol/Symbol.iterator
+- Spread
Until these are polyfilled appropriately, they should not be used. Please
update this list with additional unsupported features.
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index adf8795a5e3..01a0044f096 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -625,9 +625,9 @@ This is a bit of a difficult question to answer, because the definition of "bad"
is relative to the problem you are trying to solve. However, some patterns are
best avoided in most cases, such as:
-* Sequential scans on large tables
-* Filters that remove a lot of rows
-* Performing a certain step (e.g. an index scan) that requires _a lot_ of
+- Sequential scans on large tables
+- Filters that remove a lot of rows
+- Performing a certain step (e.g. an index scan) that requires _a lot_ of
buffers (e.g. more than 512 MB for GitLab.com).
As a general guideline, aim for a query that:
@@ -650,8 +650,8 @@ different queries. The only _rule_ is that you _must always measure_ your query
(preferably using a production-like database) using `EXPLAIN (ANALYZE, BUFFERS)`
and related tools such as:
-* <https://explain.depesz.com/>
-* <http://tatiyants.com/postgres-query-plan-visualization/>
+- <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
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index e5466ae8914..0e396baccff 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -4,7 +4,7 @@ We developed a number of utilities to ease development.
## [`MergeHash`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/merge_hash.rb)
-* Deep merges an array of hashes:
+- Deep merges an array of hashes:
``` ruby
Gitlab::Utils::MergeHash.merge(
@@ -31,7 +31,7 @@ We developed a number of utilities to ease development.
]
```
-* Extracts all keys and values from a hash into an array:
+- Extracts all keys and values from a hash into an array:
``` ruby
Gitlab::Utils::MergeHash.crush(
@@ -47,14 +47,14 @@ We developed a number of utilities to ease development.
## [`Override`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/override.rb)
-* This utility could help us check if a particular method would override
+- This utility could help us check if a particular method would override
another method or not. It has the same idea of Java's `@Override` annotation
or Scala's `override` keyword. However we only do this check when
`ENV['STATIC_VERIFICATION']` is set to avoid production runtime overhead.
This is useful to check:
- * If we have typos in overriding methods.
- * If we renamed the overridden methods, making original overriding methods
+ - If we have typos in overriding methods.
+ - If we renamed the overridden methods, making original overriding methods
overrides nothing.
Here's a simple example:
@@ -92,7 +92,7 @@ We developed a number of utilities to ease development.
## [`StrongMemoize`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/strong_memoize.rb)
-* Memoize the value even if it is `nil` or `false`.
+- Memoize the value even if it is `nil` or `false`.
We often do `@value ||= compute`, however this doesn't work well if
`compute` might eventually give `nil` and we don't want to compute again.
@@ -126,7 +126,7 @@ We developed a number of utilities to ease development.
end
```
-* Clear memoization
+- Clear memoization
``` ruby
class Find
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
index ffdeff47d4a..661ab9cef1a 100644
--- a/doc/development/verifying_database_capabilities.md
+++ b/doc/development/verifying_database_capabilities.md
@@ -6,9 +6,9 @@ necessary to add database (version) specific behaviour.
To facilitate this we have the following methods that you can use:
-* `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
-* `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
-* `Gitlab::Database.version`: returns the PostgreSQL version number as a string
+- `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
+- `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
+- `Gitlab::Database.version`: returns the PostgreSQL version number as a string
in the format `X.Y.Z`. This method does not work for MySQL
This allows you to write code such as:
diff --git a/doc/install/installation.md b/doc/install/installation.md
index c913474b638..5653c59f576 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -431,9 +431,9 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners:
-* Export `RUBYOPT=--disable-gems` environment variable for the processes
-* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
-* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707)
+- Export `RUBYOPT=--disable-gems` environment variable for the processes.
+- Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
+- Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707).
### Install gitlab-workhorse
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 498b702cab1..2d9c7f15634 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -32,7 +32,7 @@ The deployment includes:
## Limitations
[High Availability](../../administration/high_availability/README.md) and
-[Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) are not supported.
+[Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) are not supported.
## Requirements
diff --git a/doc/install/structure.md b/doc/install/structure.md
index d58b0040eef..8fc6ab4ab2f 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -9,10 +9,10 @@ This is the directory structure you will end up with following the instructions
| |-- 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)**
+- `/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.*
diff --git a/doc/integration/gmail_action_buttons_for_gitlab.md b/doc/integration/gmail_action_buttons_for_gitlab.md
index c19320471e3..cc5d444c069 100644
--- a/doc/integration/gmail_action_buttons_for_gitlab.md
+++ b/doc/integration/gmail_action_buttons_for_gitlab.md
@@ -14,9 +14,9 @@ To get this functioning, you need to be registered with Google.
Pay close attention to:
-* Email account used by GitLab to send notification emails needs to have "Consistent history of sending a high volume of mail from your domain (order of hundred emails a day minimum to Gmail) for a few weeks at least".
-* "A very very low rate of spam complaints from users."
-* Emails must be authenticated via DKIM or SPF
-* Before sending the final form("Gmail Schema Whitelist Request"), you must send a real email from your production server. This means that you will have to find a way to send this email from the email address you are registering. You can do this by, for example, forwarding the real email from the email address you are registering or going into the rails console on the GitLab server and triggering the email sending from there.
+- Email account used by GitLab to send notification emails needs to have "Consistent history of sending a high volume of mail from your domain (order of hundred emails a day minimum to Gmail) for a few weeks at least".
+- "A very very low rate of spam complaints from users."
+- Emails must be authenticated via DKIM or SPF.
+- Before sending the final form ("Gmail Schema Whitelist Request"), you must send a real email from your production server. This means that you will have to find a way to send this email from the email address you are registering. You can do this by, for example, forwarding the real email from the email address you are registering or going into the rails console on the GitLab server and triggering the email sending from there.
You can check how it looks going through all the steps laid out in the "Registering with Google" doc in [this GitLab.com issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/1517).
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 4e1d5ba9b35..f5f1f9486c2 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -66,7 +66,7 @@ that are in common for all providers that we need to consider.
To change these settings:
-* **For omnibus package**
+- **For omnibus package**
Open the configuration file:
@@ -89,7 +89,7 @@ To change these settings:
gitlab_rails['omniauth_block_auto_created_users'] = true
```
-* **For installations from source**
+- **For installations from source**
Open the configuration file:
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 03ba2ae8817..1b93cdb83ac 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -15,9 +15,9 @@ GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
For example, for GitLab version 10.5.7:
-* `10` represents major version
-* `5` represents minor version
-* `7` represents patch number
+- `10` represents major version
+- `5` represents minor version
+- `7` represents patch number
## Patch releases
@@ -55,13 +55,13 @@ cases you need to consider.
It is considered safe to jump between patch versions and minor versions within
one major version. For example, it is safe to:
-* Upgrade the patch version:
- * `8.9.0` -> `8.9.7`
- * `8.9.0` -> `8.9.1`
- * `8.9.2` -> `8.9.6`
-* Upgrade the minor version:
- * `8.9.4` -> `8.12.3`
- * `9.2.3` -> `9.5.5`
+- Upgrade the patch version:
+ - `8.9.0` -> `8.9.7`
+ - `8.9.0` -> `8.9.1`
+ - `8.9.2` -> `8.9.6`
+- Upgrade the minor version:
+ - `8.9.4` -> `8.12.3`
+ - `9.2.3` -> `9.5.5`
Upgrading the major version requires more attention.
We cannot guarantee that upgrading between major versions will be seamless. As previously mentioned, major versions are reserved for backwards incompatible changes.
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
index 94ba5d1375d..d61a205d954 100644
--- a/doc/security/crime_vulnerability.md
+++ b/doc/security/crime_vulnerability.md
@@ -17,8 +17,8 @@ GitLab supports both gzip and [SPDY][ngx-spdy] and mitigates the CRIME
vulnerability by deactivating gzip when HTTPS is enabled. You can see the
sources of the files in question:
-* [Source installation NGINX file][source-nginx]
-* [Omnibus installation NGINX file][omnibus-nginx]
+- [Source installation NGINX file][source-nginx]
+- [Omnibus installation NGINX file][omnibus-nginx]
Although SPDY is enabled in Omnibus installations, CRIME relies on compression
(the 'C') and the default compression level in NGINX's SPDY module is 0
@@ -52,9 +52,9 @@ vulnerability.
### References
-* Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
-* Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
-* Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
+- Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
+- Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
+- Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
[source-nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/gitlab-ssl
[omnibus-nginx]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index a96dd6aa9f0..780e9b8783e 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -539,9 +539,9 @@ a helm pre-upgrade hook.
For example, in a Rails application:
-* `DB_INITIALIZE` can be set to `cd /app && RAILS_ENV=production
+- `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_MIGRATE` can be set to `cd /app && RAILS_ENV=production bin/update`
NOTE: **Note:**
The `/app` path is the directory of your project inside the docker image
@@ -632,7 +632,7 @@ repo or by specifying a project variable:
one](https://gitlab.com/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)
- `AUTO_DEVOPS_CHART` with the URL of a custom chart to use.
+ `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.
### Customizing `.gitlab-ci.yml`
@@ -678,6 +678,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| ------------ | --------------- |
| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
| `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_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `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 |
@@ -702,6 +703,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
| `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. |
TIP: **Tip:**
Set up the replica variables using a
@@ -713,6 +715,63 @@ You should *not* scale your application using Kubernetes directly. This can
cause confusion with Helm not detecting the change, and subsequent deploys with
Auto DevOps can undo your changes.
+#### Application secret variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
+
+Some applications need to define secret variables that are
+accessible by the deployed application. Auto DevOps detects variables where the key starts with
+`K8S_SECRET_` and make these prefixed variables available to the
+deployed application, as environment variables.
+
+To configure your application variables:
+
+1. Go to your project's **Settings > CI/CD**, then expand the section
+ called **Variables**.
+
+2. Create a CI Variable, ensuring the key is prefixed with
+ `K8S_SECRET_`. For example, you can create a variable with key
+`K8S_SECRET_RAILS_MASTER_KEY`.
+
+3. Run an Auto Devops pipeline either by manually creating a new
+ pipeline or by pushing a code change to GitLab.
+
+Auto DevOps pipelines will take your application secret variables to
+populate a Kubernetes secret. This secret is unique per environment.
+When deploying your application, the secret is loaded as environment
+variables in the container running the application. Following the
+example above, you can see the secret below containing the
+`RAILS_MASTER_KEY` variable.
+
+```sh
+$ kubectl get secret production-secret -n minimal-ruby-app-54 -o yaml
+apiVersion: v1
+data:
+ RAILS_MASTER_KEY: MTIzNC10ZXN0
+kind: Secret
+metadata:
+ creationTimestamp: 2018-12-20T01:48:26Z
+ name: production-secret
+ namespace: minimal-ruby-app-54
+ resourceVersion: "429422"
+ selfLink: /api/v1/namespaces/minimal-ruby-app-54/secrets/production-secret
+ uid: 57ac2bfd-03f9-11e9-b812-42010a9400e4
+type: Opaque
+```
+
+CAUTION: **Caution:**
+Variables with multiline values are not currently supported due to
+limitations with the current Auto DevOps scripting environment.
+
+NOTE: **Note:**
+Environment variables are generally considered immutable in a Kubernetes
+pod. Therefore, if you update an application secret without changing any
+code then manually create a new pipeline, you will find that any running
+application pods will not have the updated secrets. In this case, you
+can either push a code update to GitLab to force the Kubernetes
+Deployment to recreate pods or manually delete running pods to
+cause Kubernetes to create new pods with updated secrets.
+
#### Advanced replica variables setup
Apart from the two replica-related variables for production mentioned above,
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 7c7e44d29e7..d34cd1bb1c3 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -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/gitlab-geo/README.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](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.
### GitLab High Availability
@@ -303,7 +303,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use
### 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](https://docs.gitlab.com/ee/integration/jenkins.html).
### Jira
@@ -407,7 +407,7 @@ A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi
#### Mount
-External reference:
+External reference:
As stated on the [wikipedia page](https://en.wikipedia.org/wiki/Mount_(Unix)), "Mounting makes file systems, files, directories, devices and special files available for use and available to the user."
@@ -447,7 +447,7 @@ Software for which the original source code is freely [available](https://openso
#### Open Source Stewardship
-[Related blog post](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
+[Related blog post](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
### Owner
@@ -557,7 +557,7 @@ Software that is hosted centrally and accessed on-demand (i.e. whenever you want
This term is often used by people when they mean "Version Control."
-### Scrum
+### Scrum
An Agile [framework](https://www.scrum.org/Resources/What-is-Scrum) designed to typically help complete complex software projects. It's made up of several parts: product requirements backlog, sprint planning, sprint (development), sprint review, and retrospec (analyzing the sprint). The goal is to end up with potentially shippable products.
@@ -697,7 +697,7 @@ A [website/system](http://www.wiki.com/) that allows for collaborative editing o
### Working area
-Files that have been modified but are not committed. Check them by using the command "git status".
+Files that have been modified but are not committed. Check them by using the command "git status".
### Working Tree
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index b21cf27c1d3..77a1892b656 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -66,10 +66,10 @@ RDS instances as well.
The subnets are listed with their name, AZ and CIDR block:
-* gitlab-public-10.0.0.0 - us-west-2a - 10.0.0.0
-* gitlab-private-10.0.1.0 - us-west-2a - 10.0.1.0
-* gitlab-public-10.0.2.0 - us-west-2b - 10.0.2.0
-* gitlab-private-10.0.3.0 - us-west-2b - 10.0.3.0
+- gitlab-public-10.0.0.0 - us-west-2a - 10.0.0.0
+- gitlab-private-10.0.1.0 - us-west-2a - 10.0.1.0
+- gitlab-public-10.0.2.0 - us-west-2b - 10.0.2.0
+- gitlab-private-10.0.3.0 - us-west-2b - 10.0.3.0
### Route Table
@@ -161,7 +161,7 @@ private subnets.
Now press the Launch a Cache Cluster and choose Redis for our
DB engine. You'll be able to configure details such as replication,
Multi-AZ and node types. The second section will allow us to choose our
-subnet and security group and
+subnet and security group and
![Redis Cluster details](img/redis-cluster-det.png)
@@ -206,7 +206,7 @@ http traffic from anywhere and name it something such as
`gitlab-ec2-security-group`.
While we wait for it to launch we can allocate an Elastic IP and
-associate it with our new EC2 instance.
+associate it with our new EC2 instance.
### RDS and Redis Security Group
@@ -268,8 +268,8 @@ our current case we'll specify the adapter, encoding, host, db name,
username, and password.
gitlab_rails['db_adapter'] = "postgresql"
- gitlab_rails['db_encoding'] = "unicode"
- gitlab_rails['db_database'] = "gitlabhq_production"
+ gitlab_rails['db_encoding'] = "unicode"
+ gitlab_rails['db_database'] = "gitlabhq_production"
gitlab_rails['db_username'] = "gitlab"
gitlab_rails['db_password'] = "mypassword"
gitlab_rails['db_host'] = "<rds-endpoint>"
@@ -288,9 +288,9 @@ to make the EFS integration easier to manage.
Finally, run reconfigure. You might find it useful to run a check and
a service status to make sure everything has been set up correctly.
- sudo gitlab-ctl reconfigure
- sudo gitlab-rake gitlab:check
- sudo gitlab-ctl status
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-rake gitlab:check
+ sudo gitlab-ctl status
If everything looks good copy the Elastic IP over to your browser and
test the instance manually.
@@ -395,5 +395,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](http://docs.gitlab.com/ee/gitlab-geo/README.html)
+- [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)
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 701533358c8..637e4e3c791 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -242,10 +242,11 @@ git push origin squash_some_bugs
---
### Merge Conflicts
-* Happen often
-* Learning to fix conflicts is hard
-* Practice makes perfect
-* Force push after fixing conflicts. Be careful!
+
+- Happen often
+- Learning to fix conflicts is hard
+- Practice makes perfect
+- Force push after fixing conflicts. Be careful!
---
@@ -306,10 +307,10 @@ Create a merge request on the GitLab web UI. You'll see a conflict warning.
### Notes
-* When to use `git merge` and when to use `git rebase`
-* Rebase when updating your branch with master
-* Merge when bringing changes from feature to master
-* Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
+- When to use `git merge` and when to use `git rebase`
+- Rebase when updating your branch with master
+- Merge when bringing changes from feature to master
+- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
---
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index 66cb08feacb..d76ff57bfa3 100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
@@ -8,12 +8,12 @@ comments: false
## Instantiating Repositories
-* Create a new repository by instantiating it through
+- Create a new repository by instantiating it through:
```bash
git init
```
-* Copy an existing project by cloning the repository through
+- Copy an existing project by cloning the repository through:
```bash
git clone <url>
@@ -23,9 +23,9 @@ comments: false
## Central Repos
-* To instantiate a central repository a `--bare` flag is required.
-* Bare repositories don't allow file editing or committing changes.
-* Create a bare repo with
+- To instantiate a central repository a `--bare` flag is required.
+- Bare repositories don't allow file editing or committing changes.
+- Create a bare repo with:
```bash
git init --bare project-name.git
@@ -97,5 +97,5 @@ git log
## Note
-* git fetch vs pull
-* Pull is git fetch + git merge
+- git fetch vs pull
+- Pull is git fetch + git merge
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index b1483e725fe..e02a7deab91 100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
@@ -10,13 +10,13 @@ comments: false
Adds content to the index or staging area.
-* Adds a list of file
+- Adds a list of file:
```bash
git add <files>
```
-* Adds all files including deleted ones
+- Adds all files including deleted ones:
```bash
git add -A
@@ -26,19 +26,19 @@ Adds content to the index or staging area.
## Git add continued
-* Add all text files in current dir
+- Add all text files in current dir:
```bash
git add *.txt
```
-* Add all text file in the project
+- Add all text file in the project:
```bash
git add "*.txt*"
```
-* Adds all files in directory
+- Adds all files in directory:
```bash
git add views/layouts/
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 6ba6f9eb69d..127fdf4d44a 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -8,19 +8,19 @@ comments: false
Git log lists commit history. It allows searching and filtering.
-* Initiate log
+- Initiate log:
```
git log
```
-* Retrieve set number of records:
+- Retrieve set number of records:
```
git log -n 2
```
-* Search commits by author. Allows user name or a regular expression.
+- Search commits by author. Allows user name or a regular expression.
```
git log --author="user_name"
@@ -28,13 +28,13 @@ Git log lists commit history. It allows searching and filtering.
----------
-* Search by comment message.
+- Search by comment message:
```
git log --grep="<pattern>"
```
-* Search by date
+- Search by date:
```
git log --since=1.month.ago --until=3.weeks.ago
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index 071baddf508..a7d42904229 100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
@@ -68,7 +68,8 @@ git push origin conflicts_branch -f
----------
## Note
-* When to use 'git merge' and when to use 'git rebase'
-* Rebase when updating your branch with master
-* Merge when bringing changes from feature to master
-* Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
+
+- When to use 'git merge' and when to use 'git rebase'
+- Rebase when updating your branch with master
+- Merge when bringing changes from feature to master
+- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index 44304634f36..ca3a64e4347 100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
@@ -8,13 +8,13 @@ comments: false
## Undo Commits
-* Undo last commit putting everything back into the staging area.
+- Undo last commit putting everything back into the staging area:
```
git reset --soft HEAD^
```
-* Add files and change message with:
+- Add files and change message with:
```
git commit --amend -m "New Message"
@@ -22,13 +22,13 @@ comments: false
----------
-* Undo last and remove changes
+- Undo last and remove changes:
```
git reset --hard HEAD^
```
-* Same as last one but for two commits back
+- Same as last one but for two commits back:
```
git reset --hard HEAD^^
@@ -73,9 +73,9 @@ git push origin master
## Note
-* git revert vs git reset
-* Reset removes the commit while revert removes the changes but leaves the commit
-* Revert is safer considering we can revert a revert
+- git revert vs git reset
+- Reset removes the commit while revert removes the changes but leaves the commit
+- Revert is safer considering we can revert a revert
```
# Changed file
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index 42eedea14e5..f1c91fb1b37 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -9,7 +9,7 @@ comments: false
We use git stash to store our changes when they are not ready to be committed
and we need to change to a different branch.
-* Stash
+- Stash:
```
git stash save
@@ -19,7 +19,7 @@ and we need to change to a different branch.
git stash save "this is a message to display on the list"
```
-* Apply stash to keep working on it
+- Apply stash to keep working on it:
```
git stash apply
@@ -29,7 +29,7 @@ and we need to change to a different branch.
----------
-* Every time we save a stash it gets stacked so by using list we can see all our
+- Every time we save a stash it gets stacked so by using list we can see all our
stashes.
```
@@ -38,7 +38,7 @@ stashes.
git stash list --stat
```
-* To clean our stack we need to manually remove them.
+- To clean our stack we need to manually remove them:
```
# drop top stash
@@ -51,15 +51,14 @@ stashes.
----------
-* Apply and drop on one command
+- Apply and drop on one command:
```
git stash pop
```
-* If we meet conflicts we need to either reset or commit our changes.
-
-* Conflicts through `pop` will not drop a stash afterwards.
+- If we meet conflicts we need to either reset or commit our changes.
+- Conflicts through `pop` will not drop a stash afterwards.
----------
diff --git a/doc/university/training/topics/subtree.md b/doc/university/training/topics/subtree.md
index b5a892dc17b..ba7c3394938 100644
--- a/doc/university/training/topics/subtree.md
+++ b/doc/university/training/topics/subtree.md
@@ -4,20 +4,20 @@ comments: false
# Subtree
-* Used when there are nested repositories.
-* Not recommended when the amount of dependencies is too large
-* For these cases we need a dependency control system
-* Command are painfully long so aliases are necessary
+- Used when there are nested repositories.
+- Not recommended when the amount of dependencies is too large.
+- For these cases we need a dependency control system.
+- Command are painfully long so aliases are necessary.
----------
## Subtree Aliases
-* Add: git subtree add --prefix <target-folder> <url> <branch> --squash
-* Pull: git subtree add --prefix <target-folder> <url> <branch> --squash
-* Push: git subtree add --prefix <target-folder> <url> <branch>
-* Ex: git config alias.sbp 'subtree pull --prefix st /
- git@gitlab.com:balameb/subtree-nested-example.git master --squash'
+- Add: git subtree add --prefix <target-folder> <url> <branch> --squash.
+- Pull: git subtree add --prefix <target-folder> <url> <branch> --squash.
+- Push: git subtree add --prefix <target-folder> <url> <branch>.
+- Ex: git config alias.sbp 'subtree pull --prefix st /
+ git@gitlab.com:balameb/subtree-nested-example.git master --squash'.
----------
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index ee7913637b9..da36a3218e5 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.
```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:
+- 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:
```bash
git checkout -- <file>
@@ -22,14 +22,14 @@ comments: false
----------
-* To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag.
+- To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
```
git rm '*.txt'
git rm -r <dirname>
```
-* If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our `.gitignore` file then use `--cache`.
+- If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our `.gitignore` file then use `--cache`:
```
git rm <filename> --cache
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index 781c90e4198..7f3abf74675 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -110,8 +110,8 @@ There are new configuration options available for gitlab.yml. View them with the
git diff origin/6-9-stable:config/gitlab.yml.example origin/7-0-stable:config/gitlab.yml.example
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting.
### 7. Start application
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index 6fcec5b7974..c20a72ce162 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -178,16 +178,16 @@ TIP: to see what changed in `gitlab.yml.example` in this release use next comman
git diff 6-0-stable:config/gitlab.yml.example 7-14-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example but with your settings.
-* Copy rack attack middleware config
+- Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example but with your settings.
+- Copy rack attack middleware config.
```bash
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
```
-* Set up logrotate
+- Set up logrotate
```bash
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
@@ -195,9 +195,9 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab-ssl but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab-ssl but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
### Check the version of /usr/local/bin/git
diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md
index 07f92ac3af6..b69bd391241 100644
--- a/doc/update/7.1-to-7.2.md
+++ b/doc/update/7.1-to-7.2.md
@@ -94,8 +94,8 @@ There are new configuration options available for `gitlab.yml`. View them with t
git diff 7-1-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting.
Update rack attack middleware config
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index a16f9de54e4..b69a9927f37 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -108,8 +108,8 @@ git diff origin/7-2-stable:config/gitlab.yml.example origin/7-3-stable:config/gi
sudo -u git -H sed -i 's/:backlog => 64/:backlog => 1024/' config/unicorn.rb
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your setting.
### 7. Start application
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
index 734c655f1d1..3786095bb8b 100644
--- a/doc/update/7.3-to-7.4.md
+++ b/doc/update/7.3-to-7.4.md
@@ -75,11 +75,11 @@ sudo -u git -H editor config/unicorn.rb
#### Change Nginx HTTPS settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting.
#### MySQL Databases: Update database.yml config file
-* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql][mysql]:
+- Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql][mysql]:
```
sudo -u git -H editor config/database.yml
@@ -136,7 +136,7 @@ SET foreign_key_checks = 1;
# Find MySQL users
mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
-# If git user exists and gitlab user does not exist
+# If git user exists and gitlab user does not exist
# you are done with the database cleanup tasks
mysql> \q
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
index 7a3a49ff948..d93d32d1b60 100644
--- a/doc/update/7.4-to-7.5.md
+++ b/doc/update/7.4-to-7.5.md
@@ -77,8 +77,8 @@ git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
### 6. Start application
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
index 0d45a9528b9..0e87918c8c0 100644
--- a/doc/update/7.5-to-7.6.md
+++ b/doc/update/7.5-to-7.6.md
@@ -79,8 +79,8 @@ git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
#### Set up time zone (optional)
diff --git a/doc/update/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md
index 5e0b2ca7bcd..c24b5647f21 100644
--- a/doc/update/7.6-to-7.7.md
+++ b/doc/update/7.6-to-7.7.md
@@ -79,8 +79,8 @@ git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
#### Set up time zone (optional)
diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md
index f5b1ebf0a9c..61bd5fb1298 100644
--- a/doc/update/7.7-to-7.8.md
+++ b/doc/update/7.7-to-7.8.md
@@ -79,9 +79,9 @@ git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
index 0db7698936b..c13dd5b60e6 100644
--- a/doc/update/7.8-to-7.9.md
+++ b/doc/update/7.8-to-7.9.md
@@ -81,9 +81,9 @@ git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/7.9-to-7.10.md b/doc/update/7.9-to-7.10.md
index 782fb0736e6..4dece93652e 100644
--- a/doc/update/7.9-to-7.10.md
+++ b/doc/update/7.9-to-7.10.md
@@ -77,9 +77,9 @@ git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/g
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index f009906256e..51178809b4c 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -176,10 +176,10 @@ if ($ENV{"SLONYSET"}) {
In this configuration file you should replace a few placeholders before you can
use it. The following placeholders should be replaced:
-* `OLD_HOST`: the address of the old database server.
-* `NEW_HOST`: the address of the new database server.
-* `SLONY_PASSWORD`: the password of the Slony user created earlier.
-* `TABLES`: the tables to replicate.
+- `OLD_HOST`: the address of the old database server.
+- `NEW_HOST`: the address of the new database server.
+- `SLONY_PASSWORD`: the password of the Slony user created earlier.
+- `TABLES`: the tables to replicate.
The list of tables to replicate can be generated by running the following
command on your old PostgreSQL database:
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index bd0155dc712..e165a120162 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -12,9 +12,9 @@ If enabled, version check will inform you if a new version is available and the
importance of it through a status. This is shown on the help page (i.e. `/help`)
for all signed in users, and on the admin pages. The statuses are:
-* Green: You are running the latest version of GitLab.
-* Orange: An updated version of GitLab is available.
-* Red: The version of GitLab you are running is vulnerable. You should install
+- Green: You are running the latest version of GitLab.
+- Orange: An updated version of GitLab is available.
+- Red: The version of GitLab you are running is vulnerable. You should install
the latest version with security fixes as soon as possible.
![Orange version check example](img/update-available.png)
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index e14e716a5eb..68a0f1a5837 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -281,12 +281,14 @@ of proposed changes can be found at
GitLab.com adjusts the memory limits for the [unicorn-worker-killer][unicorn-worker-killer] gem.
Base default:
-* `memory_limit_min` = 750MiB
-* `memory_limit_max` = 1024MiB
+
+- `memory_limit_min` = 750MiB
+- `memory_limit_max` = 1024MiB
Web front-ends:
-* `memory_limit_min` = 1024MiB
-* `memory_limit_max` = 1280MiB
+
+- `memory_limit_min` = 1024MiB
+- `memory_limit_max` = 1280MiB
## GitLab.com at scale
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 943b0c693c0..4d56b33f684 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -21,8 +21,8 @@ Nested groups are only supported when you use PostgreSQL. Supporting nested
groups on MySQL in an efficient way is not possible due to MySQL's limitations.
See the following links for more information:
-* <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
-* <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10885>
+- <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
+- <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10885>
## Overview
diff --git a/doc/user/instance_statistics/user_cohorts.md b/doc/user/instance_statistics/user_cohorts.md
index 70d5912dc4e..f52f24ef5f7 100644
--- a/doc/user/instance_statistics/user_cohorts.md
+++ b/doc/user/instance_statistics/user_cohorts.md
@@ -23,5 +23,5 @@ the month, but who have never actually had any activity in the instance.
How do we measure the activity of users? GitLab considers a user active if:
-* the user signs in
-* the user has Git activity (whether push or pull).
+- The user signs in.
+- The user has Git activity (whether push or pull).
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 76f7e869ff7..83bc79925e1 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -43,8 +43,8 @@ or a U2F device.
1. Install a compatible application. We recommend [Google Authenticator]
\(proprietary\) or [FreeOTP] \(open source\).
1. In the application, add a new entry in one of two ways:
- * Scan the code with your phone's camera to add the entry automatically.
- * Enter the details provided to add the entry manually.
+ - Scan the code with your phone's camera to add the entry automatically.
+ - Enter the details provided to add the entry manually.
**In GitLab:**
@@ -106,7 +106,7 @@ Enter the pin from your one time password authenticator's application or a recov
### Log in via U2F device
-1. Click **Login via U2F Device**
+1. Click **Login via U2F Device**.
1. A light will start blinking on your device. Activate it by pressing its button.
You will see a message indicating that your device responded to the authentication request.
@@ -135,9 +135,9 @@ authenticate with Git over HTTPS on the command line or when using
To disable two-factor authentication on your account (for example, if you
have lost your code generation device) you can:
-* [Use a saved recovery code](#use-a-saved-recovery-code)
-* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh)
-* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
+- [Use a saved recovery code](#use-a-saved-recovery-code).
+- [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh).
+- [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account).
### Use a saved recovery code
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index cac64fc0cb6..eb9e1cd85cd 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -283,9 +283,9 @@ In the example above, we see the following trace on the mitmproxy window:
The above image shows:
-* The initial PUT requests went through fine with a 201 status code.
-* The 201 redirected the client to the S3 bucket.
-* The HEAD request to the AWS bucket reported a 403 Unauthorized.
+- The initial PUT requests went through fine with a 201 status code.
+- The 201 redirected the client to the S3 bucket.
+- The HEAD request to the AWS bucket reported a 403 Unauthorized.
What does this mean? This strongly suggests that the S3 user does not have the right
[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html).
diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png
index 7bb088aad0b..242460925f7 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/import/github.md b/doc/user/project/import/github.md
index 42da2210fab..b36373b4830 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -18,16 +18,16 @@ GitHub without the constraints of a Sidekiq worker.
The following aspects of a project are imported:
- * Repository description (GitLab.com & 7.7+)
- * Git repository data (GitLab.com & 7.7+)
- * Issues (GitLab.com & 7.7+)
- * Pull requests (GitLab.com & 8.4+)
- * Wiki pages (GitLab.com & 8.4+)
- * Milestones (GitLab.com & 8.7+)
- * Labels (GitLab.com & 8.7+)
- * Release note descriptions (GitLab.com & 8.12+)
- * Pull request review comments (GitLab.com & 10.2+)
- * Regular issue and pull request comments
+- Repository description (GitLab.com & 7.7+)
+- Git repository data (GitLab.com & 7.7+)
+- Issues (GitLab.com & 7.7+)
+- Pull requests (GitLab.com & 8.4+)
+- Wiki pages (GitLab.com & 8.4+)
+- Milestones (GitLab.com & 8.7+)
+- Labels (GitLab.com & 8.7+)
+- Release note descriptions (GitLab.com & 8.12+)
+- Pull request review comments (GitLab.com & 10.2+)
+- Regular issue and pull request comments
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
each imported repository maintains visibility level unless that [visibility
@@ -132,8 +132,8 @@ Admin access to the GitLab server is required.
For large projects it may take a while to import all data. To reduce the time necessary, you can increase the number of
Sidekiq workers that process the following queues:
-* `github_importer`
-* `github_importer_advance_stage`
+- `github_importer`
+- `github_importer_advance_stage`
For an optimal experience, it's recommended having at least 4 Sidekiq processes (each running a number of threads equal
to the number of CPU cores) that *only* process these queues. It's also recommended that these processes run on separate
diff --git a/doc/user/project/integrations/emails_on_push.md b/doc/user/project/integrations/emails_on_push.md
index df320de7e16..61359dcaa90 100644
--- a/doc/user/project/integrations/emails_on_push.md
+++ b/doc/user/project/integrations/emails_on_push.md
@@ -10,10 +10,10 @@ In the _Recipients_ area, provide a list of emails separated by spaces or newlin
The following options are available:
-+ **Push events** - Email will be triggered when a push event is received
-+ **Tag push events** - Email will be triggered when a tag is created and pushed
-+ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
-+ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
+- **Push events** - Email will be triggered when a push event is received.
+- **Tag push events** - Email will be triggered when a tag is created and pushed.
+- **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
+- **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
| Settings | Notification |
| --- | --- |
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index bc4bba40e59..754711f5919 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -31,7 +31,7 @@ directly from GitLab, as covered in the article
Each GitLab project can be configured to connect to an entire Jira instance. That
means one GitLab project can interact with _all_ Jira projects in that instance, once
-configured. Therefore, you will not have to explicitly associate
+configured. Therefore, you will not have to explicitly associate
a GitLab project with any single Jira project.
If you have one Jira instance, you can pre-fill the settings page with a default
@@ -46,10 +46,12 @@ project in Jira and then enter the correct values in GitLab.
### Configuring Jira
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 an 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 an user in JIRA cloud](jira_cloud_configuration.md)
### Configuring GitLab
@@ -114,11 +116,11 @@ USER mentioned this issue in RESOURCE_NAME of [PROJECT_NAME|LINK_TO_COMMENT]:
ENTITY_TITLE
```
-* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
-* `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
-* `RESOURCE_NAME` Kind of resource which referenced the issue. Can be a commit or merge request.
-* `PROJECT_NAME` GitLab project name.
-* `ENTITY_TITLE` Merge request title or commit message first line.
+- `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
+- `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
+- `RESOURCE_NAME` Kind of resource which referenced the issue. Can be a commit or merge request.
+- `PROJECT_NAME` GitLab project name.
+- `ENTITY_TITLE` Merge request title or commit message first line.
![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
@@ -190,11 +192,11 @@ Make sure that the Jira issue is not already marked as resolved; that is,
the Jira issue resolution field is not set. (It should not be struck through in
Jira lists.)
-### CAPTCHA
+### CAPTCHA
-CAPTCHA may be triggered after several consecutive failed login attempts
+CAPTCHA may be triggered after several consecutive failed login attempts
which may lead to a `401 unauthorized` error when testing your Jira integration.
-If CAPTCHA has been triggered, you will not be able to use Jira's REST API to
+If CAPTCHA has been triggered, you will not be able to use Jira's REST API to
authenticate with the Jira site. You will need to log in to your Jira instance
and complete the CAPTCHA.
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 5de8e66e7eb..d6ee678443f 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -9,8 +9,9 @@ within the GitLab interface.
![Environment Dashboard](img/prometheus_dashboard.png)
There are two ways to set up Prometheus integration, depending on where your apps are running:
-* 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).
+
+- 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).
@@ -23,8 +24,8 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl
#### Requirements
-* A [connected Kubernetes cluster](../clusters/index.md)
-* Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
+- A [connected Kubernetes cluster](../clusters/index.md)
+- Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
#### Getting started
@@ -42,9 +43,9 @@ Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [offi
The Prometheus server will [automatically detect and monitor](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Ckubernetes_sd_config%3E) nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus, simply set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/):
-* `prometheus.io/scrape` to `true` to enable monitoring of the resource.
-* `prometheus.io/port` to define the port of the metrics endpoint.
-* `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
+- `prometheus.io/scrape` to `true` to enable monitoring of the resource.
+- `prometheus.io/port` to define the port of the metrics endpoint.
+- `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
CPU and Memory consumption is monitored, but requires [naming conventions](prometheus_library/kubernetes.html#specifying-the-environment) in order to determine the environment. If you are using [Auto DevOps](../../../topics/autodevops/), this is handled automatically.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 40ac855c74f..d5f77d622be 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -21,18 +21,20 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integration](../../clusters/index.md#installing-applications), it will [automatically be monitored](#about-managed-nginx-ingress-deployments) by Prometheus.
For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-* NGINX Ingress should be version 0.9.0 or above, with metrics enabled
-* NGINX Ingress should be annotated for Prometheus monitoring
-* Prometheus should be configured to monitor annotated pods
+
+- NGINX Ingress should be version 0.9.0 or above, with metrics enabled
+- NGINX Ingress should be annotated for Prometheus monitoring
+- Prometheus should be configured to monitor annotated pods
### 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 is configured for Prometheus monitoring, by setting:
-* `enable-vts-status: "true"`, to export Prometheus metrics
-* `prometheus.io/scrape: "true"`, to enable automatic discovery
-* `prometheus.io/port: "10254"`, to specify the metrics port
+
+- `enable-vts-status: "true"`, to export Prometheus metrics
+- `prometheus.io/scrape: "true"`, to enable automatic discovery
+- `prometheus.io/port: "10254"`, to specify the metrics port
When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
@@ -42,8 +44,8 @@ Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress
Next, the ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
-* `prometheus.io/scrape: "true"`
-* `prometheus.io/port: "10254"`
+- `prometheus.io/scrape: "true"`
+- `prometheus.io/port: "10254"`
Managing these settings depends on how NGINX ingress has been deployed. If you have deployed via the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress), metrics can be enabled with `controller.stats.enabled` along with the required annotations. Alternatively it is possible edit the NGINX ingress YML directly in the [Kubernetes dashboard](https://github.com/kubernetes/dashboard).
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 9e2434c02ec..7962eeada5c 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -175,6 +175,7 @@ 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.
+Using the search box at the top of the menu, you can filter the listed boards.
When you're revisiting an issue board in a project or group with multiple boards,
GitLab will automatically load the last board you visited.
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index c33d1365001..2bf4fa287e9 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -39,34 +39,40 @@ It opens a new issue for that project labeled after its respective list.
## New issue via email
-*This feature needs [incoming email](../../../administration/incoming_email.md)
-to be configured by a GitLab administrator to be available for CE/EE users, and
-it's available on GitLab.com.*
+At the bottom of a project's Issues List page, a link to **Email a new issue to this project**
+is displayed if your GitLab instance has [incoming email](../../../administration/incoming_email.md) configured.
-At the bottom of a project's issue page, click
-**Email a new issue to this project**, and you will find an email address
-which belongs to you. You could add this address to your contact.
+![Bottom of a project issues page](img/new_issue_from_email.png)
+
+When you click this link, an email address is displayed which belongs to you for creating issues in this project.
+You can save this address as a contact in your email client for easy acceess.
-This is a private email address, generated just for you.
-**Keep it to yourself** as anyone who gets ahold of it can create issues or
-merge requests as if they were you. You can add this address to your contact
-list for easy access.
+CAUTION: **Caution:**
+This is a private email address, generated just for you. **Keep it to yourself**,
+as anyone who gets ahold of it can create issues or merge requests as if they
+were you. If the address is compromised, or you'd like it to be regenerated for
+any reason, click **Email a new issue to this project** again and click the reset link.
Sending an email to this address will create a new issue on your behalf for
-this project, where the email subject becomes the issue title, and the email
-body becomes the issue description. [Markdown] and [quick actions] are
-supported.
+this project, where:
-![Bottom of a project issues page](img/new_issue_from_email.png)
+- The email subject becomes the issue title.
+- The email body becomes the issue description.
+- [Markdown](../../markdown.md) and [quick actions](../quick_actions.md) are supported.
+
+NOTE: **Note:**
+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 URL with prefilled fields
You can link directly to the new issue page for a given project, with prefilled
-field values using query string parameters in a URL. This is useful for embedding
-a URL in an external HTML page, and also certain scenarios where you want the user to
+field values using query string parameters in a URL. This is useful for embedding
+a URL in an external HTML page, and also certain scenarios where you want the user to
create an issue with certain fields prefilled.
-The title, description, and description template fields can be prefilled using
+The title, description, and description template fields can be prefilled using
this method. The description and description template fields cannot be pre-entered
in the same URL (since a description template just populates the description field).
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
new file mode 100644
index 00000000000..001e0d303e9
--- /dev/null
+++ b/doc/user/project/issues/csv_import.md
@@ -0,0 +1,45 @@
+# Importing Issues from CSV
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7.
+
+Issues can be imported by uploading a CSV file. The file will be processed in the background and a notification email
+will be sent to you once the import is completed.
+
+> **Note:** A permission level of `Developer` or higher is required to import issues.
+
+## CSV File Format
+
+### Header row
+
+CSV files must contain a header row with at least two columns: `title` and `description`, in that order.
+
+### Column separator
+
+The column separator is automatically detected from the header row.
+
+Supported separator characters are: commas (`,`), semicolons (`;`), and tabs (`\t`).
+
+### Row separator
+
+Lines ending in either `CRLF` or `LF` are supported.
+
+### Quote character
+
+The double-quote (`"`) character is used to quote fields so you can use the column separator within a field. To insert
+a double-quote (`"`) within a quoted field, use two double-quote characters in succession, i.e. `""`.
+
+### Data rows
+
+After the header row, succeeding rows must follow the same column order. The issue title is required while the
+description is optional.
+
+The user uploading the CSV file will be set as the author of the imported issues.
+
+## Sample Data
+
+```csv
+title,description
+My Issue Title,My Issue Description
+Another Title,"A description, with a comma"
+"One More Title","One More Description"
+```
diff --git a/doc/user/project/issues/img/import_csv_button.png b/doc/user/project/issues/img/import_csv_button.png
new file mode 100644
index 00000000000..ab100a95750
--- /dev/null
+++ b/doc/user/project/issues/img/import_csv_button.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 200b3a642a1..40a1f60c4ab 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -142,6 +142,15 @@ to find out more about this feature.
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).
+### Import Issues from CSV
+
+From the project-level issues list, you can find the import button near the "Edit issues" button in the upper-right
+side.
+
+![Import CSV button](img/import_csv_button.png)
+
+Learn more about [importing issues from CSV](csv_import.md)
+
### External Issue Tracker
Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
diff --git a/doc/user/project/merge_requests/img/create_from_email.png b/doc/user/project/merge_requests/img/create_from_email.png
index 610f0b3d0c1..5cb2afaf976 100644
--- a/doc/user/project/merge_requests/img/create_from_email.png
+++ b/doc/user/project/merge_requests/img/create_from_email.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index d4f8cf929f6..f479f9e4ef6 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -169,9 +169,9 @@ those conflicts in the GitLab UI.
## Create new merge requests by email
-*This feature needs [incoming email](../../../administration/incoming_email.md)
+_This feature needs [incoming email](../../../administration/incoming_email.md)
to be configured by a GitLab administrator to be available for CE/EE users, and
-it's available on GitLab.com.*
+it's available on GitLab.com._
You can create a new merge request by sending an email to a user-specific email
address. The address can be obtained on the merge requests page by clicking on
@@ -183,8 +183,16 @@ will be used as the merge request description. You need
this feature. If it's not enabled to your instance, you may ask your GitLab
administrator to do so.
+This is a private email address, generated just for you. **Keep it to yourself**
+as anyone who gets ahold of it can create issues or merge requests as if they were you.
+You can add this address to your contact list for easy access.
+
![Create new merge requests by email](img/create_from_email.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._
+
### Adding patches when creating a merge request via e-mail
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22723) in GitLab 11.5.
@@ -320,8 +328,8 @@ troubleshooting steps.
This can occur for one of two reasons:
-* Sidekiq doesn't pick up the changes fast enough
-* Because of the bug described in [#41545](https://gitlab.com/gitlab-org/gitlab-ce/issues/41545)
+- Sidekiq doesn't pick up the changes fast enough
+- Because of the bug described in [#41545](https://gitlab.com/gitlab-org/gitlab-ce/issues/41545)
#### Sidekiq
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 2ec423dcf70..1b57331dbe7 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -18,7 +18,7 @@ Into a single commit on merge:
![A squashed commit followed by a merge commit][squashed-commit]
-The squashed commit's commit message is the merge request title. And note that
+The squashed commit's commit message is the merge request title. And note that
the squashed commit is still followed by a merge commit, as the merge
method for this example repository uses a merge commit. Squashing also works
with the fast-forward merge strategy, see
@@ -30,7 +30,7 @@ details.
When working on a feature branch, you sometimes want to commit your current
progress, but don't really care about the commit messages. Those 'work in
progress commits' don't necessarily contain important information and as such
-you'd rather not include them in your target branch.
+you'd rather not include them in your target branch.
With squash and merge, when the merge request is ready to be merged,
all you have to do is enable squashing before you press merge to join
@@ -56,9 +56,9 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata:
-* Message: the title of the merge request.
-* Author: the author of the merge request.
-* Committer: the user who initiated the squash.
+- Message: the title of the merge request.
+- Author: the author of the merge request.
+- Committer: the user who initiated the squash.
## Squash and fast-forward merge
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 15eacc48dfe..88d745b0ce4 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -45,10 +45,10 @@ this filepath should be **relative** to the root.
Here are some valid examples:
-> * .gitlab-ci.yml
-> * .my-custom-file.yml
-> * my/path/.gitlab-ci.yml
-> * my/path/.my-custom-file.yml
+- `.gitlab-ci.yml`
+- `.my-custom-file.yml`
+- `my/path/.gitlab-ci.yml`
+- `my/path/.my-custom-file.yml`
## Test coverage parsing
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 78c1294346b..770cef42995 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -31,7 +31,7 @@ on the search field on the top-right of your screen:
If you want to search for issues present in a specific project, navigate to
a project's **Issues** tab, and click on the field **Search or filter results...**. It will
-display a dropdown menu, from which you can add filters per author, assignee, milestone,
+display a dropdown menu, from which you can add filters per author, assignee, milestone,
label, weight, and 'my-reaction' (based on your emoji votes). When done, press **Enter** on your keyboard to filter the issues.
![filter issues in a project](img/issue_search_filter.png)
@@ -54,12 +54,12 @@ Selecting **Any** does the opposite. It returns results that have a non-empty va
You can filter issues and merge requests by specific terms included in titles or descriptions.
-* Syntax
- * Searches look for all the words in a query, in any order. E.g.: searching
+- Syntax
+ - 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 term, use double quotes: `"display bug"`
-* Limitation
- * For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
+ - To find the exact term, use double quotes: `"display bug"`
+- Limitation
+ - For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
issues for `included in titles` is same as `included titles`
![filter issues by specific terms](img/issue_search_by_term.png)
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index ec5943fd51b..3fb553280d0 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -4,9 +4,9 @@ Documentation on how to use Git LFS are under [Managing large binary files with
## Requirements
-* Git LFS is supported in GitLab starting with version 8.2.
-* Support for object storage, such as AWS S3, was introduced in 10.0.
-* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
+- Git LFS is supported in GitLab starting with version 8.2.
+- Support for object storage, such as AWS S3, was introduced in 10.0.
+- Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
## Configuration
@@ -15,9 +15,9 @@ GitLab is installed on.
There are various configuration options to help GitLab server administrators:
-* Enabling/disabling Git LFS support
-* Changing the location of LFS object storage
-* Setting up object storage supported by [Fog](http://fog.io/about/provider_documentation.html)
+- Enabling/disabling Git LFS support
+- Changing the location of LFS object storage
+- Setting up object storage supported by [Fog](http://fog.io/about/provider_documentation.html)
### Configuration for Omnibus installations
@@ -229,11 +229,11 @@ See more information in [!19581](https://gitlab.com/gitlab-org/gitlab-ce/merge_r
## Known limitations
-* Support for removing unreferenced LFS objects was added in 8.14 onwards.
-* LFS authentications via SSH was added with GitLab 8.12
-* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
-* The storage statistics currently count each LFS object multiple times for
- every project linking to it
+- Support for removing unreferenced LFS objects was added in 8.14 onwards.
+- LFS authentications via SSH was added with GitLab 8.12.
+- Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
+- The storage statistics currently count each LFS object multiple times for
+ every project linking to it.
[reconfigure gitlab]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: ../../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
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 d02e921fa84..da0243705aa 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -21,18 +21,18 @@ Documentation for GitLab instance administrators is under [LFS administration do
## Requirements
-* Git LFS is supported in GitLab starting with version 8.2
-* Git LFS must be enabled under project settings
-* [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
+- Git LFS is supported in GitLab starting with version 8.2
+- Git LFS must be enabled under project settings
+- [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
## Known limitations
-* Git LFS v1 original API is not supported since it was deprecated early in LFS
+- Git LFS v1 original API is not supported since it was deprecated early in LFS
development
-* When SSH is set as a remote, Git LFS objects still go through HTTPS
-* Any Git LFS request will ask for HTTPS credentials to be provided so a good Git
+- When SSH is set as a remote, Git LFS objects still go through HTTPS
+- Any Git LFS request will ask for HTTPS credentials to be provided so a good Git
credentials store is recommended
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
+- Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
to add the URL to Git config manually (see [troubleshooting](#troubleshooting))
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
@@ -158,16 +158,16 @@ git lfs unlock --id=123 --force
There are a couple of reasons why this error can occur:
-* You don't have permissions to access certain LFS object
+- You don't have permissions to access certain LFS object
Check if you have permissions to push to the project or fetch from the project.
-* Project is not allowed to access the LFS object
+- Project is not allowed to access the LFS object
LFS object you are trying to push to the project or fetch from the project is not
available to the project anymore. Probably the object was removed from the server.
-* Local git repository is using deprecated LFS API
+- Local git repository is using deprecated LFS API
### Invalid status for `<url>` : 501
@@ -180,15 +180,15 @@ git lfs logs last
If the status `error 501` is shown, it is because:
-* Git LFS is not enabled in project settings. Check your project settings and
+- Git LFS is not enabled in project settings. Check your project settings and
enable Git LFS.
-* Git LFS support is not enabled on the GitLab server. Check with your GitLab
+- Git LFS support is not enabled on the GitLab server. Check with your GitLab
administrator why Git LFS is not enabled on the server. See
[LFS administration documentation](lfs_administration.md) for instructions
on how to enable LFS support.
-* Git LFS client version is not supported by GitLab server. Check your Git LFS
+- Git LFS client version is not supported by GitLab server. Check your Git LFS
version with `git lfs version`. Check the Git config of the project for traces
of deprecated API with `git lfs -l`. If `batch = false` is set in the config,
remove the line and try to update your Git LFS client. Only version 1.0.1 and
diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md
index aa0d2a7f799..00cddda24a4 100644
--- a/doc/workflow/releases.md
+++ b/doc/workflow/releases.md
@@ -20,4 +20,3 @@ There are several ways to add release notes:
## Tags page with button to add or edit release notes for existing git tag
![tags](releases/tags.png)
-
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 8a75687e4f5..2bb140b99d0 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -8,8 +8,9 @@ requests within GitLab.
## Overview
Time Tracking lets you:
-* record the time spent working on an issue or a merge request,
-* add an estimate of the amount of time needed to complete an issue or a merge
+
+- Record the time spent working on an issue or a merge request.
+- Add an estimate of the amount of time needed to complete an issue or a merge
request.
You don't have to indicate an estimate to enter the time spent, and vice versa.
@@ -62,11 +63,12 @@ To remove all the time spent at once, use `/remove_time_spent`.
## Configuration
The following time units are available:
-* months (mo)
-* weeks (w)
-* days (d)
-* hours (h)
-* minutes (m)
+
+- months (mo)
+- weeks (w)
+- days (d)
+- hours (h)
+- minutes (m)
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6c1a730935a..aae54fb34bc 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -512,7 +512,7 @@ module API
# `request`. We workaround this by defining methods that returns the right
# values.
def define_params_for_grape_middleware
- self.define_singleton_method(:request) { Rack::Request.new(env) }
+ self.define_singleton_method(:request) { ActionDispatch::Request.new(env) }
self.define_singleton_method(:params) { request.params.symbolize_keys }
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f5d21d8923f..9f3a1699146 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -475,7 +475,7 @@ module API
requires :file, type: File, desc: 'The file to be uploaded'
end
post ":id/uploads" do
- UploadService.new(user_project, params[:file]).execute
+ UploadService.new(user_project, params[:file]).execute.to_h
end
desc 'Get the users list of a project' do
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index d72befce571..6cf40e2d4ca 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -51,6 +51,19 @@ module Gitlab
migration_class_for(class_name).new.perform(*arguments)
end
+ def self.exists?(migration_class)
+ enqueued = Sidekiq::Queue.new(self.queue)
+ scheduled = Sidekiq::ScheduledSet.new
+
+ [enqueued, scheduled].each do |queue|
+ queue.each do |job|
+ return true if job.queue == self.queue && job.args.first == migration_class
+ end
+ end
+
+ false
+ end
+
def self.migration_class_for(class_name)
const_get(class_name)
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index b5350f56f9c..e292641da2b 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -793,7 +793,7 @@ rollout 100%:
fi
helm init --client-only
- helm repo add gitlab https://charts.gitlab.io
+ helm repo add gitlab ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io}
if [[ ! -d "$auto_chart" ]]; then
helm fetch ${auto_chart} --untar
fi
diff --git a/lib/gitlab/diff/lines_unfolder.rb b/lib/gitlab/diff/lines_unfolder.rb
index 9306b7e16a2..6cf904b2b2a 100644
--- a/lib/gitlab/diff/lines_unfolder.rb
+++ b/lib/gitlab/diff/lines_unfolder.rb
@@ -158,9 +158,14 @@ module Gitlab
from = comment_position - UNFOLD_CONTEXT_SIZE
- # There's no line before the match if it's in the top-most
- # position.
- prev_line_number = line_before_unfold_position&.old_pos || 0
+ prev_line_number =
+ if bottom?
+ last_line.old_pos
+ else
+ # There's no line before the match if it's in the top-most
+ # position.
+ line_before_unfold_position&.old_pos || 0
+ end
if from <= prev_line_number + 1
@generate_top_match_line = false
diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb
index a826519b2dd..3323ce60158 100644
--- a/lib/gitlab/email/attachment_uploader.rb
+++ b/lib/gitlab/email/attachment_uploader.rb
@@ -23,8 +23,8 @@ module Gitlab
content_type: attachment.content_type
}
- link = UploadService.new(project, file).execute
- attachments << link if link
+ uploader = UploadService.new(project, file).execute
+ attachments << uploader.to_h if uploader
ensure
tmp.close!
end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 0341f930b9c..a11d6b66409 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def call(env)
- request = Rack::Request.new(env)
+ request = ActionDispatch::Request.new(env)
route = Gitlab::EtagCaching::Router.match(request.path_info)
return @app.call(env) unless route
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index a1a374cef4a..7987533978c 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -74,6 +74,7 @@ project_tree:
- :prometheus_metrics
- :project_badges
- :ci_cd_settings
+ - :error_tracking_setting
# Only include the following attributes for the models specified.
included_attributes:
@@ -162,6 +163,9 @@ excluded_attributes:
- :token_encrypted
services:
- :template
+ error_tracking_setting:
+ - :encrypted_token
+ - :encrypted_token_iv
methods:
labels:
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index bce12103cce..099b488f68e 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -24,6 +24,7 @@ module Gitlab
project_badges: 'Badge',
metrics: 'MergeRequest::Metrics',
ci_cd_settings: 'ProjectCiCdSetting',
+ error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting',
links: 'Releases::Link' }.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
diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb
index 474e9d45566..e232198150a 100644
--- a/lib/gitlab/import_export/uploads_manager.rb
+++ b/lib/gitlab/import_export/uploads_manager.rb
@@ -40,7 +40,7 @@ module Gitlab
def add_upload(upload)
uploader_context = FileUploader.extract_dynamic_path(upload).named_captures.symbolize_keys
- UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute
+ UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute.to_h
end
def copy_project_uploads
diff --git a/lib/gitlab/middleware/basic_health_check.rb b/lib/gitlab/middleware/basic_health_check.rb
index f2a03217098..acf8c301b8f 100644
--- a/lib/gitlab/middleware/basic_health_check.rb
+++ b/lib/gitlab/middleware/basic_health_check.rb
@@ -24,7 +24,7 @@ module Gitlab
def call(env)
return @app.call(env) unless env['PATH_INFO'] == HEALTH_PATH
- request = Rack::Request.new(env)
+ request = ActionDispatch::Request.new(env)
return OK_RESPONSE if client_ip_whitelisted?(request)
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 89941a9efa0..f142f9da43d 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -60,7 +60,7 @@ module Gitlab
end
def request
- @env['rack.request'] ||= Rack::Request.new(@env)
+ @env['actionpack.request'] ||= ActionDispatch::Request.new(@env)
end
def last_visited_url
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
index 6b0808f5304..56007574b1b 100644
--- a/lib/gitlab/repository_cache.rb
+++ b/lib/gitlab/repository_cache.rb
@@ -7,13 +7,13 @@ module Gitlab
def initialize(repository, extra_namespace: nil, backend: Rails.cache)
@repository = repository
- @namespace = "project:#{repository.project.id}"
+ @namespace = "#{repository.full_path}:#{repository.project.id}"
@namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
@backend = backend
end
def cache_key(type)
- "#{namespace}:#{type}"
+ "#{type}:#{namespace}"
end
def expire(key)
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
index f8f8ec789ce..d9811e036d3 100644
--- a/lib/gitlab/request_context.rb
+++ b/lib/gitlab/request_context.rb
@@ -13,7 +13,7 @@ module Gitlab
end
def call(env)
- req = Rack::Request.new(env)
+ req = ActionDispatch::Request.new(env)
Gitlab::SafeRequestStore[:client_ip] = req.ip
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 84a51773276..8e2f16271eb 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -26,6 +26,19 @@ module Gitlab
puts "\nOK".color(:green)
end
+ def self.without_gitaly_timeout
+ # Remove Gitaly timeout
+ old_timeout = Gitlab::CurrentSettings.current_application_settings.gitaly_timeout_default
+ Gitlab::CurrentSettings.current_application_settings.update_columns(gitaly_timeout_default: 0)
+ # Otherwise we still see the default value when running seed_fu
+ ApplicationSetting.expire
+
+ yield
+ ensure
+ Gitlab::CurrentSettings.current_application_settings.update_columns(gitaly_timeout_default: old_timeout)
+ ApplicationSetting.expire
+ end
+
def self.mute_notifications
NotificationService.prepend(MuteNotifications)
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 44c71f8431d..9b7b0db9525 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,16 +8,18 @@ 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)
+ 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?
# 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)
- port = uri.port || uri.default_port
+ port = get_port(uri)
validate_protocol!(uri.scheme, protocols)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
@@ -50,6 +52,18 @@ module Gitlab
private
+ def get_port(uri)
+ uri.port || uri.default_port
+ end
+
+ def validate_html_tags!(uri)
+ uri_str = uri.to_s
+ sanitized_uri = ActionController::Base.helpers.sanitize(uri_str, tags: [])
+ if sanitized_uri != uri_str
+ raise BlockedUrlError, 'HTML/CSS/JS tags are not allowed'
+ end
+ end
+
def parse_url(url)
raise Addressable::URI::InvalidURIError if multiline?(url)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 092c37d96bf..59e9dd67774 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -185,6 +185,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(external source)"
+msgstr ""
+
msgid "+ %{count} more"
msgstr ""
@@ -372,6 +375,9 @@ msgstr ""
msgid "Add README"
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 ""
@@ -444,6 +450,9 @@ msgstr ""
msgid "AdminProjects|Delete project"
msgstr ""
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -705,6 +714,9 @@ 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 ""
@@ -759,6 +771,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Attach a file"
+msgstr ""
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
@@ -768,6 +783,9 @@ msgstr ""
msgid "August"
msgstr ""
+msgid "Auth Token"
+msgstr ""
+
msgid "Authentication Log"
msgstr ""
@@ -1890,6 +1908,12 @@ msgstr ""
msgid "Comment"
msgstr ""
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
msgid "Comment & resolve discussion"
msgstr ""
@@ -2387,6 +2411,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
+msgid "Delete this attachment"
+msgstr ""
+
msgid "Deleted"
msgstr ""
@@ -2599,6 +2626,12 @@ 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 "Dismiss"
msgstr ""
@@ -2686,6 +2719,9 @@ msgstr ""
msgid "Edit identity for %{user_name}"
msgstr ""
+msgid "Edit issues"
+msgstr ""
+
msgid "Email"
msgstr ""
@@ -2761,6 +2797,9 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
+msgid "Enter your Sentry API URL"
+msgstr ""
+
msgid "Environment variables"
msgstr ""
@@ -2863,6 +2902,9 @@ msgstr ""
msgid "Error Reporting and Logging"
msgstr ""
+msgid "Error Tracking"
+msgstr ""
+
msgid "Error fetching contributors data."
msgstr ""
@@ -3070,6 +3112,9 @@ msgstr ""
msgid "File templates"
msgstr ""
+msgid "File upload error."
+msgstr ""
+
msgid "Files"
msgstr ""
@@ -3085,6 +3130,9 @@ msgstr ""
msgid "Filter..."
msgstr ""
+msgid "Find and manage Auth Tokens in your Sentry account settings page."
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -3100,6 +3148,9 @@ msgstr ""
msgid "Fingerprints"
msgstr ""
+msgid "Finish editing this message first!"
+msgstr ""
+
msgid "Finished"
msgstr ""
@@ -3576,6 +3627,9 @@ msgstr ""
msgid "Import"
msgstr ""
+msgid "Import CSV"
+msgstr ""
+
msgid "Import Projects from Gitea"
msgstr ""
@@ -3594,6 +3648,9 @@ msgstr ""
msgid "Import in progress"
msgstr ""
+msgid "Import issues"
+msgstr ""
+
msgid "Import multiple repositories by uploading a manifest file."
msgstr ""
@@ -3732,6 +3789,9 @@ msgstr ""
msgid "Issues, merge requests, pushes and comments."
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 ""
@@ -4066,6 +4126,9 @@ msgstr ""
msgid "Mark todo as done"
msgstr ""
+msgid "Markdown"
+msgstr ""
+
msgid "Markdown enabled"
msgstr ""
@@ -4664,6 +4727,9 @@ msgstr ""
msgid "Open Documentation"
msgstr ""
+msgid "Open comment type dropdown"
+msgstr ""
+
msgid "Open in Xcode"
msgstr ""
@@ -4958,6 +5024,9 @@ 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 ""
@@ -5820,6 +5889,9 @@ msgstr ""
msgid "Save changes"
msgstr ""
+msgid "Save comment"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
@@ -5973,6 +6045,9 @@ msgstr ""
msgid "Send usage data"
msgstr ""
+msgid "Sentry API URL"
+msgstr ""
+
msgid "Sep"
msgstr ""
@@ -6380,6 +6455,15 @@ 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 ""
@@ -6443,6 +6527,12 @@ msgstr ""
msgid "Subscribe at project level"
msgstr ""
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
msgid "Subscribed"
msgstr ""
@@ -6602,7 +6692,7 @@ 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 %{max_attachment_size}mb"
+msgid "The maximum file size allowed is %{size}."
msgstr ""
msgid "The maximum file size allowed is 200KB."
@@ -6716,6 +6806,9 @@ 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 ""
@@ -7101,6 +7194,9 @@ msgstr ""
msgid "To import an SVN repository, check out %{svn_link}."
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 ""
@@ -7140,6 +7236,9 @@ msgstr ""
msgid "Toggle commit description"
msgstr ""
+msgid "Toggle commit list"
+msgstr ""
+
msgid "Toggle discussion"
msgstr ""
@@ -7290,6 +7389,9 @@ msgstr ""
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
+msgid "Upload CSV file"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -7644,6 +7746,9 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -7836,6 +7941,12 @@ 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 ""
@@ -7854,6 +7965,9 @@ msgstr ""
msgid "assign yourself"
msgstr ""
+msgid "attach a new file"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -7918,6 +8032,9 @@ msgstr ""
msgid "here"
msgstr ""
+msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/issues/"
+msgstr ""
+
msgid "https://your-bitbucket-server"
msgstr ""
@@ -7962,6 +8079,9 @@ msgstr[1] ""
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 ""
@@ -8121,6 +8241,9 @@ 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 ""
@@ -8182,6 +8305,12 @@ msgstr ""
msgid "project"
msgstr ""
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
msgid "remaining"
msgstr ""
@@ -8202,6 +8331,9 @@ msgstr ""
msgid "show less"
msgstr ""
+msgid "sign in"
+msgstr ""
+
msgid "source"
msgstr ""
diff --git a/package.json b/package.json
index bc7bc0880de..5c4abacae99 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"@babel/preset-env": "^7.1.0",
"@gitlab/csslab": "^1.8.0",
"@gitlab/svgs": "^1.47.0",
- "@gitlab/ui": "^1.18.0",
+ "@gitlab/ui": "^1.20.0",
"apollo-boost": "^0.1.20",
"apollo-client": "^2.4.5",
"autosize": "^4.0.0",
@@ -89,7 +89,7 @@
"sanitize-html": "^1.16.1",
"select2": "3.5.2-browserify",
"sha1": "^1.1.1",
- "smooshpack": "^0.0.53",
+ "smooshpack": "^0.0.54",
"sortablejs": "^1.7.0",
"sql.js": "^0.4.0",
"stickyfilljs": "^2.0.5",
diff --git a/qa/README.md b/qa/README.md
index 08ba59e117d..5e32496ea9f 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -86,7 +86,7 @@ The environment variable `QA_COOKIES` can be set to send additional cookies
on every request. This is necessary on gitlab.com to direct traffic to the
canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`.
-To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
+To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
### Building a Docker image to test
@@ -100,3 +100,24 @@ docker build -t gitlab/gitlab-ce-qa:nightly .
```
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
+
+### Quarantined tests
+
+Tests can be put in quarantine by assigning `:quarantine` metadata. This means
+they will be skipped unless run with `--tag quarantine`. This can be used for
+tests that are expected to fail while a fix is in progress (similar to how
+[`skip` or `pending`](https://relishapp.com/rspec/rspec-core/v/3-8/docs/pending-and-skipped-examples)
+ can be used).
+
+```
+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
+least one of the tags other than `quarantine`. This is different from how RSpec
+tags usually work, where all tags are inclusive.
+
+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`.
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 0a48f4c0e7f..c3c90f254b7 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -116,6 +116,14 @@ module QA
has_css?(element_selector_css(name), wait: wait)
end
+ def has_no_element?(name, wait: Capybara.default_max_wait_time)
+ has_no_css?(element_selector_css(name), wait: wait)
+ end
+
+ def has_text?(text)
+ page.has_text? text
+ end
+
def has_no_text?(text)
page.has_no_text? text
end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 6d07d5a10e6..98bcb96b92c 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -6,6 +6,12 @@ module QA
find('.select2-result-label', text: item_text).click
end
+ def clear_current_selection_if_present
+ if has_css?('a > abbr.select2-search-choice-close', wait: 1.0)
+ find('a > abbr.select2-search-choice-close').click
+ end
+ end
+
def search_and_select(item_text)
find('.select2-input').set(item_text)
select_item(item_text)
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 6acc413b586..a588af07e4a 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -5,6 +5,7 @@ module QA
include Page::Component::Select2
view 'app/views/projects/new.html.haml' do
+ element :project_create_from_template_tab
element :import_project_tab, "Import project" # rubocop:disable QA/ElementWithPattern
end
@@ -44,6 +45,10 @@ module QA
click_on 'Create project'
end
+ def go_to_create_from_template
+ click_element(:project_create_from_template_tab)
+ end
+
def set_visibility(visibility)
choose visibility
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 23e580b81b6..a3e126b51da 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -66,11 +66,29 @@ module QA
def commit_changes
click_element :begin_commit_button
- click_element :commit_button
+ # After clicking :begin_commit_button 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.
wait(reload: false) do
- page.has_content?('Your changes have been committed')
+ has_no_element?(:begin_commit_button) &&
+ has_element?(:commit_button)
end
+
+ # Retry the attempt to click :commit_button just in case part of the
+ # animation is still in process even when the buttons have the
+ # expected visibility.
+ commit_success_msg_shown = with_retry do
+ click_element :commit_button
+
+ wait(reload: false) do
+ has_text?('Your changes have been committed')
+ end
+ end
+
+ raise "The changes do not appear to have been committed successfully." unless commit_success_msg_shown
end
end
end
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index f9f71aa4a72..9fea74eabc9 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -11,7 +11,7 @@ module QA
wait(reload: false) do
click_button 'Expand' unless first('button', text: 'Collapse')
- page.has_content?('Collapse')
+ has_content?('Collapse')
end
yield if block_given?
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 6cd5c06a088..5ee8df03d50 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
@@ -10,37 +10,35 @@ module QA
Page::Main::Login.act { sign_in_using_credentials }
end
- before(:all) do
- login
+ [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
+ @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
+ # 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
- # 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
+ # 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
- Page::Project::Show.act { wait_for_push }
- end
+ Page::Project::Show.act { wait_for_push }
- [true, false].each do |rbac|
- context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
- before(:all) do
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new(rbac: rbac).create!
kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster|
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index cfccbb910b7..e96756642c8 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -88,6 +88,22 @@ module QA
found
end
+ def has_no_element?(name, wait: Capybara.default_max_wait_time)
+ found = super
+
+ log("has_no_element? :#{name} returned #{found}")
+
+ found
+ end
+
+ def has_text?(text)
+ found = super
+
+ log(%Q{has_text?('#{text}') returned #{found}})
+
+ found
+ end
+
def has_no_text?(text)
found = super
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index f108a5ca318..2eb826becea 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -74,6 +74,20 @@ describe QA::Support::Page::Logging do
.to output(/has_element\? :element 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
+ end
+
+ it 'logs has_text?' do
+ allow(page).to receive(:has_text?).and_return(true)
+
+ expect { subject.has_text? 'foo' }
+ .to output(/has_text\?\('foo'\) returned true/).to_stdout_from_any_process
+ end
+
it 'logs has_no_text?' do
allow(page).to receive(:has_no_text?).with('foo').and_return(true)
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 8e01da01340..3537ba7c235 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -5,6 +5,24 @@ Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
+
+ # If quarantine is tagged, skip tests that have other metadata unless
+ # they're also tagged. 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.
+ if config.inclusion_filter[:quarantine]
+ skip("Running tests tagged with all of #{config.inclusion_filter.rules.keys}") unless quarantine_and_optional_other_tag?(example, config)
+ end
+ end
+
+ config.before(:each, :quarantine) do |example|
+ # Skip tests in quarantine unless we explicitly focus on them
+ # 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('In quarantine') unless config.inclusion_filter[:quarantine]
end
config.expect_with :rspec do |expectations|
@@ -22,3 +40,19 @@ RSpec.configure do |config|
config.order = :random
Kernel.srand config.seed
end
+
+# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
+#
+# Returns true if
+# - the example 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?(example, config)
+ return false unless example.metadata.keys.include? :quarantine
+
+ filters_other_than_quarantine = config.inclusion_filter.rules.keys.reject { |key| key == :quarantine }
+
+ return true if filters_other_than_quarantine.empty?
+
+ filters_other_than_quarantine.any? { |key| example.metadata.keys.include? key }
+end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
new file mode 100644
index 00000000000..f001200fb52
--- /dev/null
+++ b/qa/spec/spec_helper_spec.rb
@@ -0,0 +1,264 @@
+# 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 'foo', :foo do
+ it_behaves_like 'passing tests'
+ end
+
+ context 'default' do
+ it_behaves_like 'passing tests'
+ end
+ end
+ end
+
+ context 'default config' do
+ it 'tests are skipped if in quarantine' do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = foo_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')
+
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to eq(2)
+
+ ex = default_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = default_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 "with 'quarantine' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = :quarantine
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "only quarantined tests are run" do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to be(1)
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to be(1)
+
+ ex = default_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context "with 'foo' tagged" 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
+
+ it "tests are not run if not tagged 'foo'" do
+ default_context = group.children.find { |c| c.description == "default" }
+ expect(default_context.descendant_filtered_examples.count).to eq(0)
+ end
+
+ it "tests are skipped if in quarantine" do
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = foo_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 "with 'quarantine' and 'foo' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { quarantine: true, foo: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it 'of tests tagged foo, only tests in quarantine run' do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+
+ it 'if tests are not tagged they are skipped, even if they are in quarantine' do
+ group.run
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to eq(1)
+
+ ex = default_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ end
+ end
+
+ context "with 'foo' and 'bar' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { bar: true, foo: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "runs tests tagged either 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'foo', :foo do
+ end
+ example 'bar', :bar do
+ end
+ example 'foo and bar', :foo, :bar do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(3)
+
+ ex = group.examples.find { |e| e.description == "foo" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "bar" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "foo and bar" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+
+ it "skips quarantined tests tagged 'foo' and/or 'bar'" do
+ group = RSpec.describe do
+ example 'foo in quarantine', :foo, :quarantine do
+ end
+ example 'foo and bar in quarantine', :foo, :bar, :quarantine do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(2)
+
+ ex = group.examples.find { |e| e.description == "foo in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+
+ ex = group.examples.find { |e| e.description == "foo and bar in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+
+ it "ignores quarantined tests not tagged either 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'in quarantine', :quarantine do
+ end
+ end
+
+ group.run
+
+ ex = group.examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to be_nil
+ end
+ end
+
+ context "with 'foo' and 'bar' and 'quarantined' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { bar: true, foo: true, quarantine: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "runs tests tagged 'quarantine' and 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'foo', :foo do
+ end
+ example 'bar and quarantine', :bar, :quarantine do
+ end
+ example 'foo and bar', :foo, :bar do
+ end
+ example 'foo, bar, and quarantine', :foo, :bar, :quarantine do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(4)
+
+ ex = group.examples.find { |e| e.description == "foo" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+
+ ex = group.examples.find { |e| e.description == "bar and quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "foo and bar" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+
+ ex = group.examples.find { |e| e.description == "foo, bar, and quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index a239ac16c0d..df21dc7bc85 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1026,6 +1026,72 @@ describe Projects::IssuesController do
end
end
+ describe 'POST #import_csv' do
+ let(:project) { create(:project, :public) }
+ let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
+
+ context 'feature disabled' do
+ it 'returns 404' do
+ sign_in(user)
+ project.add_maintainer(user)
+
+ stub_feature_flags(issues_import_csv: false)
+
+ import_csv
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+ end
+
+ context 'unauthorized' do
+ it 'returns 404 for guests' do
+ sign_out(:user)
+
+ import_csv
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+
+ it 'returns 404 for project members with reporter role' do
+ sign_in(user)
+ project.add_reporter(user)
+
+ import_csv
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+ end
+
+ context 'authorized' do
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ end
+
+ it "returns 302 for project members with developer role" do
+ import_csv
+
+ expect(flash[:notice]).to include('Your issues are being imported')
+ expect(response).to redirect_to(project_issues_path(project))
+ end
+
+ it "shows error when upload fails" do
+ allow_any_instance_of(UploadService).to receive(:execute).and_return(nil)
+
+ import_csv
+
+ expect(flash[:alert]).to include('File upload error.')
+ expect(response).to redirect_to(project_issues_path(project))
+ end
+ end
+
+ def import_csv
+ post :import_csv, namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ file: file
+ end
+ end
+
describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
context 'when authenticated' do
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index ac93393ac3a..f031a74c5bd 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -71,7 +71,7 @@ describe Projects::MergeRequests::CreationsController do
expect(response).to be_success
total = assigns(:total_commit_count)
- expect(assigns(:commits)).to be_an Array
+ expect(assigns(:commits)).to be_an CommitCollection
expect(total).to be > 0
expect(assigns(:hidden_commit_count)).to eq(0)
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index fbb26de76d1..810f5bb64ba 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -11,25 +11,171 @@ describe Projects::Settings::OperationsController do
project.add_maintainer(user)
end
- describe 'GET #show' do
- it 'returns 404' do
- get :show, params: project_params(project)
+ context 'error tracking' do
+ describe 'GET #show' do
+ it 'renders show template' do
+ get :show, params: project_params(project)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
+
+ context 'with existing setting' do
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'loads existing setting' do
+ get :show, params: project_params(project)
+
+ expect(controller.helpers.error_tracking_setting)
+ .to eq(error_tracking_setting)
+ 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
+ end
+ end
+
+ context 'with feature flag disabled' do
+ before do
+ stub_feature_flags(error_tracking: false)
+ end
+
+ it 'renders 404' do
+ get :show, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'renders 404' do
+ get :show, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signup page' do
+ get :show, params: project_params(project)
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ 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'
+ }
+ }
+ end
+ let(:error_tracking_permitted) do
+ ActionController::Parameters.new(error_tracking_params).permit!
+ end
+
+ context 'when update succeeds' do
+ before do
+ stub_operations_update_service_returning(status: :success)
+ end
+
+ it 'shows a notice' do
+ patch :update, params: project_params(project, error_tracking_params)
+
+ expect(response).to redirect_to(operations_url)
+ expect(flash[:notice]).to eq _('Your changes have been saved')
+ end
+ end
+
+ context 'when update fails' do
+ before do
+ stub_operations_update_service_returning(status: :error)
+ end
+
+ it 'renders show page' do
+ patch :update, params: project_params(project, error_tracking_params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
+ end
+
+ context 'with feature flag disabled' do
+ before do
+ stub_feature_flags(error_tracking: false)
+ end
+
+ it 'renders 404' do
+ patch :update, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'renders 404' do
+ patch :update, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ 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)
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
end
- end
- describe 'PATCH #update' do
- it 'returns 404' do
- patch :update, params: project_params(project)
+ private
- expect(response).to have_gitlab_http_status(:not_found)
+ 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)
end
end
private
- def project_params(project)
- { namespace_id: project.namespace, project_id: project }
+ def project_params(project, params = {})
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ project: params
+ }
end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 7c505ee0d43..897b4411055 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -64,6 +64,7 @@ describe 'Database schema' do
let(:indexes) { connection.indexes(table) }
let(:columns) { connection.columns(table) }
let(:foreign_keys) { connection.foreign_keys(table) }
+ let(:primary_key_column) { connection.primary_key(table) }
context 'all foreign keys' do
# for index to be effective, the FK constraint has to be at first place
@@ -71,6 +72,12 @@ describe 'Database schema' do
first_indexed_column = indexes.map(&:columns).map(&:first)
foreign_keys_columns = foreign_keys.map(&:column)
+ # Add the primary key column to the list of indexed columns because
+ # postgres and mysql both automatically create an index on the primary
+ # key. Also, the rails connection.indexes() method does not return
+ # automatically generated indexes (like the primary key index).
+ first_indexed_column = first_indexed_column.push(primary_key_column)
+
expect(first_indexed_column.uniq).to include(*foreign_keys_columns)
end
end
diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb
new file mode 100644
index 00000000000..f044cbe8755
--- /dev/null
+++ b/spec/factories/project_error_tracking_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_error_tracking_setting, class: ErrorTracking::ProjectErrorTrackingSetting do
+ project
+ api_url 'https://gitlab.com'
+ enabled true
+ token 'access_token_123'
+ end
+end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 0a69a26eb3e..04f39b807d7 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -238,7 +238,7 @@ describe 'Admin updates settings' do
page.within('.as-ci-cd') do
check 'Default to Auto DevOps pipeline for all projects'
- fill_in 'Auto devops domain', with: 'domain.com'
+ fill_in 'application_setting_auto_devops_domain', with: 'domain.com'
click_button 'Save changes'
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 c559a301ca1..d56476adb05 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -12,6 +12,7 @@ describe 'Projects > Files > User browses LFS files' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
visit project_tree_path(project, 'lfs')
+ wait_for_requests
end
it 'is possible to see raw content of LFS pointer' do
@@ -26,10 +27,11 @@ describe 'Projects > Files > User browses LFS files' do
end
end
- context 'when LFS is enabled' do
+ context 'when LFS is enabled', :js do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
visit project_tree_path(project, 'lfs')
+ wait_for_requests
end
it 'shows an LFS object' do
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
new file mode 100644
index 00000000000..317ffb6a2ff
--- /dev/null
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'User views releases', :js do
+ let!(:project) { create(:project, :repository) }
+ let!(:release) { create(:release, project: project ) }
+ let!(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+
+ gitlab_sign_in(user)
+ end
+
+ it 'sees the release' do
+ visit project_releases_path(project)
+
+ expect(page).to have_content(release.name)
+ expect(page).to have_content(release.tag)
+ end
+
+ context 'when there is a link as an asset' do
+ let!(:release_link) { create(:release_link, release: release, url: url ) }
+ let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
+
+ it 'sees the link' do
+ visit project_releases_path(project)
+
+ page.within('.js-assets-list') do
+ expect(page).to have_link release_link.name, href: release_link.url
+ expect(page).not_to have_content('(external source)')
+ end
+ end
+
+ context 'when url points to external resource' do
+ let(:url) { 'http://google.com/download' }
+
+ it 'sees that the link is external resource' do
+ visit project_releases_path(project)
+
+ page.within('.js-assets-list') do
+ expect(page).to have_content('(external source)')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
new file mode 100644
index 00000000000..1f2328a6dd8
--- /dev/null
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Settings > For a forked project', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:role) { :maintainer }
+
+ before do
+ stub_feature_flags(error_tracking: true)
+ sign_in(user)
+ project.add_role(user, role)
+ end
+
+ describe 'Sidebar > Operations' do
+ context 'when sidebar feature flag enabled' do
+ it 'renders the settings link in the sidebar' do
+ visit project_path(project)
+ wait_for_requests
+
+ expect(page).to have_selector('a[title="Operations"]', visible: false)
+ end
+ end
+
+ context 'when sidebar feature flag disabled' do
+ before do
+ stub_feature_flags(error_tracking: false)
+ end
+
+ it 'does not render the settings link in the sidebar' do
+ visit project_path(project)
+ wait_for_requests
+
+ expect(page).not_to have_selector('a[title="Operations"]', visible: false)
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 193ab6821a5..1bd39a46830 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -120,7 +120,9 @@
"rebase_path": { "type": ["string", "null"] },
"squash": { "type": "boolean" },
"test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" }
+ "can_receive_suggestion": { "type": "boolean" },
+ "source_branch_protected": { "type": "boolean" },
+ "conflicts_docs_path": { "type": ["string", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/csv_comma.csv b/spec/fixtures/csv_comma.csv
new file mode 100644
index 00000000000..e477a27d243
--- /dev/null
+++ b/spec/fixtures/csv_comma.csv
@@ -0,0 +1,4 @@
+title,description
+Issue in 中文,Test description
+"Hello","World"
+"Title with quote""",Description
diff --git a/spec/fixtures/csv_semicolon.csv b/spec/fixtures/csv_semicolon.csv
new file mode 100644
index 00000000000..679797489e2
--- /dev/null
+++ b/spec/fixtures/csv_semicolon.csv
@@ -0,0 +1,5 @@
+title;description
+Issue in 中文;Test description
+Title with, comma;"Description"
+
+"Hello";"World"
diff --git a/spec/fixtures/csv_tab.csv b/spec/fixtures/csv_tab.csv
new file mode 100644
index 00000000000..f801794ea9c
--- /dev/null
+++ b/spec/fixtures/csv_tab.csv
@@ -0,0 +1,4 @@
+title description
+Issue in 中文 Test description
+ "Error Row"
+"Hello" "World"
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
index 500c19e3abb..c087352a122 100644
--- a/spec/fixtures/security-reports/master/gl-container-scanning-report.json
+++ b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
@@ -1,18 +1,92 @@
{
- "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
+ "image": "registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff",
"unapproved": [
- "CVE-2017-15651"
+ "CVE-2017-18018",
+ "CVE-2016-2781",
+ "CVE-2017-12424",
+ "CVE-2007-5686",
+ "CVE-2013-4235"
],
"vulnerabilities": [
{
- "featurename": "musl",
- "featureversion": "1.1.14-r15",
- "vulnerability": "CVE-2017-15651",
- "namespace": "alpine:v3.4",
- "description": "",
- "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15651",
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2017-18269",
+ "namespace": "debian:9",
+ "description": "SSE2-optimized memmove implementation problem.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2017-18269",
+ "severity": "Defcon1",
+ "fixedby": "2.24-11+deb9u4"
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2017-16997",
+ "namespace": "debian:9",
+ "description": "elf/dl-load.c in the GNU C Library (aka glibc or libc6) 2.19 through 2.26 mishandles RPATH and RUNPATH containing $ORIGIN for a privileged (setuid or AT_SECURE) program, which allows local users to gain privileges via a Trojan horse library in the current working directory, related to the fillin_rpath and decompose_rpath functions. This is associated with misinterpretion of an empty RPATH/RUNPATH token as the \"./\" directory. NOTE: this configuration of RPATH/RUNPATH for a privileged program is apparently very uncommon; most likely, no such program is shipped with any common Linux distribution.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2017-16997",
+ "severity": "Critical",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2018-1000001",
+ "namespace": "debian:9",
+ "description": "In glibc 2.26 and earlier there is confusion in the usage of getcwd() by realpath() which can be used to write before the destination buffer leading to a buffer underflow and potential code execution.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-1000001",
+ "severity": "High",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2016-10228",
+ "namespace": "debian:9",
+ "description": "The iconv program in the GNU C Library (aka glibc or libc6) 2.25 and earlier, when invoked with the -c option, enters an infinite loop when processing invalid multi-byte input sequences, leading to a denial of service.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2016-10228",
"severity": "Medium",
- "fixedby": "1.1.14-r16"
+ "fixedby": ""
+ },
+ {
+ "featurename": "elfutils",
+ "featureversion": "0.168-1",
+ "vulnerability": "CVE-2018-18520",
+ "namespace": "debian:9",
+ "description": "An Invalid Memory Address Dereference exists in the function elf_end in libelf in elfutils through v0.174. Although eu-size is intended to support ar files inside ar files, handle_ar in size.c closes the outer ar file before handling all inner entries. The vulnerability allows attackers to cause a denial of service (application crash) with a crafted ELF file.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-18520",
+ "severity": "Low",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2010-4052",
+ "namespace": "debian:9",
+ "description": "Stack consumption vulnerability in the regcomp implementation in the GNU C Library (aka glibc or libc6) through 2.11.3, and 2.12.x through 2.12.2, allows context-dependent attackers to cause a denial of service (resource exhaustion) via a regular expression containing adjacent repetition operators, as demonstrated by a {10,}{10,}{10,}{10,} sequence in the proftpd.gnu.c exploit for ProFTPD.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2010-4052",
+ "severity": "Negligible",
+ "fixedby": ""
+ },
+ {
+ "featurename": "nettle",
+ "featureversion": "3.3-1",
+ "vulnerability": "CVE-2018-16869",
+ "namespace": "debian:9",
+ "description": "A Bleichenbacher type side-channel based padding oracle attack was found in the way nettle handles endian conversion of RSA decrypted PKCS#1 v1.5 data. An attacker who is able to run a process on the same physical core as the victim process, could use this flaw extract plaintext or in some cases downgrade any TLS connections to a vulnerable server.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-16869",
+ "severity": "Unknown",
+ "fixedby": ""
+ },
+ {
+ "featurename": "perl",
+ "featureversion": "5.24.1-3+deb9u4",
+ "vulnerability": "CVE-2018-18311",
+ "namespace": "debian:9",
+ "description": "Perl before 5.26.3 and 5.28.x before 5.28.1 has a buffer overflow via a crafted regular expression that triggers invalid write operations.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-18311",
+ "severity": "Unknown",
+ "fixedby": "5.24.1-3+deb9u5"
}
]
-}
+} \ No newline at end of file
diff --git a/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
new file mode 100644
index 00000000000..26eae2d5e61
--- /dev/null
+++ b/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`File row header component adds multiple ellipsises after 40 characters 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets/javascripts/…/…/diffs/notes
+ </span>
+</div>
+`;
+
+exports[`File row header component renders file path 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets
+ </span>
+</div>
+`;
+
+exports[`File row header component trucates path after 40 characters 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets/javascripts/merge_requests
+ </span>
+</div>
+`;
diff --git a/spec/frontend/vue_shared/components/file_row_header_spec.js b/spec/frontend/vue_shared/components/file_row_header_spec.js
new file mode 100644
index 00000000000..80f4c275dcc
--- /dev/null
+++ b/spec/frontend/vue_shared/components/file_row_header_spec.js
@@ -0,0 +1,36 @@
+import { shallowMount } from '@vue/test-utils';
+import FileRowHeader from '~/vue_shared/components/file_row_header.vue';
+
+describe('File row header component', () => {
+ let vm;
+
+ function createComponent(path) {
+ vm = shallowMount(FileRowHeader, {
+ propsData: {
+ path,
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders file path', () => {
+ createComponent('app/assets');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('trucates path after 40 characters', () => {
+ createComponent('app/assets/javascripts/merge_requests');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('adds multiple ellipsises after 40 characters', () => {
+ createComponent('app/assets/javascripts/merge_requests/widget/diffs/notes');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index a0b380adfd6..0a903bb7519 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -26,6 +26,8 @@ describe('Diffs tree list component', () => {
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
+ localStorage.removeItem('mr_diff_tree_list');
+
vm = mountComponentWithStore(Component, { store });
});
@@ -57,6 +59,7 @@ describe('Diffs tree list component', () => {
removedLines: 0,
tempFile: true,
type: 'blob',
+ parentPath: 'app',
},
app: {
key: 'app',
@@ -121,7 +124,7 @@ describe('Diffs tree list component', () => {
vm.renderTreeList = false;
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-row').textContent).toContain('app/index.js');
+ expect(vm.$el.querySelector('.file-row').textContent).toContain('index.js');
done();
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 582535e0a53..190ca1230ca 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -230,15 +230,30 @@ describe('Diffs Module Getters', () => {
localState.treeEntries = {
file: {
type: 'blob',
+ path: 'file',
+ parentPath: '/',
+ tree: [],
},
tree: {
type: 'tree',
+ path: 'tree',
+ parentPath: '/',
+ tree: [],
},
};
expect(getters.allBlobs(localState)).toEqual([
{
- type: 'blob',
+ isHeader: true,
+ path: '/',
+ tree: [
+ {
+ parentPath: '/',
+ path: 'file',
+ tree: [],
+ type: 'blob',
+ },
+ ],
},
]);
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 4268634d302..036b320b314 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -502,6 +502,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/index.js',
name: 'index.js',
+ parentPath: 'app/',
path: 'app/index.js',
removedLines: 10,
tempFile: false,
@@ -522,6 +523,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/test/index.js',
name: 'index.js',
+ parentPath: 'app/test/',
path: 'app/test/index.js',
removedLines: 0,
tempFile: true,
@@ -535,6 +537,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/test/filepathneedstruncating.js',
name: 'filepathneedstruncating.js',
+ parentPath: 'app/test/',
path: 'app/test/filepathneedstruncating.js',
removedLines: 0,
tempFile: true,
@@ -548,6 +551,7 @@ describe('DiffsStoreUtils', () => {
},
{
key: 'package.json',
+ parentPath: '/',
path: 'package.json',
name: 'package.json',
type: 'blob',
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 6f414c8ccf1..a14031f43ed 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -205,4 +205,40 @@ describe('GfmAutoComplete', function() {
expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
});
});
+
+ describe('Issues.insertTemplateFunction', function() {
+ it('should return default template', function() {
+ expect(GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, title: 'Some Issue' })).toBe(
+ '${atwho-at}${id}', // eslint-disable-line no-template-curly-in-string
+ );
+ });
+
+ it('should return reference when reference is set', function() {
+ expect(
+ GfmAutoComplete.Issues.insertTemplateFunction({
+ id: 5,
+ title: 'Some Issue',
+ reference: 'grp/proj#5',
+ }),
+ ).toBe('grp/proj#5');
+ });
+ });
+
+ describe('Issues.templateFunction', function() {
+ it('should return html with id and title', function() {
+ expect(GfmAutoComplete.Issues.templateFunction({ id: 5, title: 'Some Issue' })).toBe(
+ '<li><small>5</small> Some Issue</li>',
+ );
+ });
+
+ it('should replace id with reference if reference is set', function() {
+ expect(
+ GfmAutoComplete.Issues.templateFunction({
+ id: 5,
+ title: 'Some Issue',
+ reference: 'grp/proj#5',
+ }),
+ ).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 1ec1e8a8dd9..f320f232687 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -716,4 +716,29 @@ describe('common_utils', () => {
expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0);
});
});
+
+ describe('isInViewport', () => {
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ });
+
+ afterEach(() => {
+ document.body.removeChild(el);
+ });
+
+ it('returns true when provided `el` is in viewport', () => {
+ document.body.appendChild(el);
+
+ expect(commonUtils.isInViewport(el)).toBe(true);
+ });
+
+ it('returns false when provided `el` is not in viewport', () => {
+ el.setAttribute('style', 'position: absolute; top: -1000px; left: -1000px;');
+ document.body.appendChild(el);
+
+ expect(commonUtils.isInViewport(el)).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/javascripts/lib/utils/datetime_utility_spec.js
index bebe76f76c5..5327ec9d2a0 100644
--- a/spec/javascripts/lib/utils/datetime_utility_spec.js
+++ b/spec/javascripts/lib/utils/datetime_utility_spec.js
@@ -156,7 +156,7 @@ describe('getSundays', () => {
});
describe('getTimeframeWindowFrom', () => {
- it('returns array of date objects upto provided length start with provided startDate', () => {
+ 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),
@@ -174,6 +174,25 @@ describe('getTimeframeWindowFrom', () => {
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', () => {
@@ -376,3 +395,22 @@ describe('calculateRemainingMilliseconds', () => {
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/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 92ebfc38722..0a266b19ea5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -135,4 +135,20 @@ describe('text_utility', () => {
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/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
index 1268cdad08d..36b181f24ef 100644
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -137,4 +137,16 @@ describe('Release block', () => {
it('renders author avatar', () => {
expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
});
+
+ describe('external label', () => {
+ it('renders external label when link is external', () => {
+ expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
+ });
+
+ it('does not render external label when link is not external', () => {
+ expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
+ 'external source',
+ );
+ });
+ });
});
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 f9cd5c8bd3c..0ddbdf67d8b 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,89 +1,154 @@
-import Vue from 'vue';
-import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+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';
describe('MRWidgetConflicts', () => {
- let Component;
let vm;
const path = '/conflicts';
+ function createComponent(propsData = {}) {
+ const localVue = createLocalVue();
+
+ vm = shallowMount(localVue.extend(ConflictsComponent), {
+ propsData,
+ });
+ }
+
beforeEach(() => {
- Component = Vue.extend(conflictsComponent);
+ spyOn($.fn, 'popover').and.callThrough();
});
afterEach(() => {
- vm.$destroy();
+ vm.destroy();
});
describe('when allowed to merge', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
+ createComponent({
mr: {
canMerge: true,
conflictResolutionPath: path,
+ conflictsDocsPath: '',
},
});
});
it('should tell you about conflicts without bothering other people', () => {
- expect(vm.$el.textContent).toContain('There are merge conflicts');
- expect(vm.$el.textContent).not.toContain('ask someone with write access');
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
- expect(resolveButton.textContent).toContain('Resolve conflicts');
- expect(resolveButton.getAttribute('href')).toEqual(path);
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
});
it('should have merge buttons', () => {
- const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
- const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
- expect(mergeButton.textContent).toContain('Merge');
- expect(mergeButton.disabled).toBeTruthy();
- expect(mergeButton.classList.contains('btn-success')).toEqual(true);
- expect(mergeLocallyButton.textContent).toContain('Merge locally');
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
describe('when user does not have permission to merge', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
+ it('should show proper message', () => {
+ createComponent({
mr: {
canMerge: false,
+ conflictsDocsPath: '',
},
});
- });
- it('should show proper message', () => {
- expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain(
- 'ask someone with write access',
- );
+ expect(
+ vm
+ .text()
+ .trim()
+ .replace(/\s\s+/g, ' '),
+ ).toContain('ask someone with write access');
});
it('should not have action buttons', () => {
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
- expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
- expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ createComponent({
+ mr: {
+ canMerge: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.contains('.js-resolve-conflicts-button')).toBe(false);
+ expect(vm.contains('.js-merge-locally-button')).toBe(false);
+ });
+
+ it('should not have resolve button when no conflict resolution path', () => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.contains('.js-resolve-conflicts-button')).toBe(false);
});
});
describe('when fast-forward or semi-linear merge enabled', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
+ it('should tell you to rebase locally', () => {
+ createComponent({
mr: {
shouldBeRebased: true,
+ conflictsDocsPath: '',
},
});
- });
- it('should tell you to rebase locally', () => {
- expect(removeBreakLine(vm.$el.textContent).trim()).toContain(
+ expect(removeBreakLine(vm.text()).trim()).toContain(
'Fast-forward merge is not possible. To merge this request, first rebase locally.',
);
});
});
+
+ describe('when source branch protected', () => {
+ beforeEach(() => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: gl.TEST_HOST,
+ sourceBranchProtected: true,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('disabled');
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).toHaveBeenCalled();
+ });
+ });
+
+ describe('when source branch not protected', () => {
+ beforeEach(() => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: gl.TEST_HOST,
+ sourceBranchProtected: false,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 67752c1c455..d1fd899c1a8 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -3,7 +3,7 @@ import FileRow from '~/vue_shared/components/file_row.vue';
import { file } from 'spec/ide/helpers';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('RepoFile', () => {
+describe('File row component', () => {
let vm;
function createComponent(propsData) {
@@ -72,39 +72,16 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
});
- describe('outputText', () => {
- beforeEach(done => {
- createComponent({
- file: {
- ...file(),
- path: 'app/assets/index.js',
- },
- level: 0,
- });
-
- vm.displayTextKey = 'path';
-
- vm.$nextTick(done);
- });
-
- it('returns text if truncateStart is 0', done => {
- vm.truncateStart = 0;
-
- vm.$nextTick(() => {
- expect(vm.outputText).toBe('app/assets/index.js');
-
- done();
- });
+ it('renders header for file', () => {
+ createComponent({
+ file: {
+ isHeader: true,
+ path: 'app/assets',
+ tree: [],
+ },
+ level: 0,
});
- it('returns text truncated at start', done => {
- vm.truncateStart = 5;
-
- vm.$nextTick(() => {
- expect(vm.outputText).toBe('...ssets/index.js');
-
- done();
- });
- });
+ expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
});
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 4e4c8b215c2..1e2aebdc84b 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => ''
}
end
- let(:request) { Rack::Request.new(env) }
+ let(:request) { ActionDispatch::Request.new(env) }
def set_param(key, value)
request.update_param(key, value)
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 4ad69aeba43..8a83b76fd94 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -119,4 +119,48 @@ describe Gitlab::BackgroundMigration do
described_class.perform('Foo', [10, 20])
end
end
+
+ describe '.exists?' do
+ context 'when there are enqueued jobs present' do
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue)]
+ end
+
+ before do
+ allow(Sidekiq::Queue).to receive(:new)
+ .with(described_class.queue)
+ .and_return(queue)
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.exists?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.exists?('Bar')).to eq(false)
+ end
+ end
+
+ context 'when there are scheduled jobs present', :sidekiq, :redis do
+ before do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(10.minutes, 'Foo')
+
+ expect(Sidekiq::ScheduledSet.new).to be_one
+ end
+ end
+
+ after do
+ Sidekiq::ScheduledSet.new.clear
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.exists?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.exists?('Bar')).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index f22c2c90334..8a470e12d04 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -301,8 +301,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -403,8 +402,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -505,8 +503,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -604,8 +601,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -729,8 +725,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -746,5 +741,105 @@ describe Gitlab::Diff::LinesUnfolder do
end
end
end
+
+ context 'position requires bottom expansion and no new match line' do
+ let(:position) do
+ Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
+ start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
+ head_sha: "1487062132228de836236c522fe52fed4980a46c",
+ old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
+ new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
+ position_type: "text",
+ old_line: 95,
+ new_line: 87)
+ end
+
+ context 'blob lines' do
+ let(:expected_blob_lines) do
+ [[94, 94, " \"url\": \"https://gitlab.gnome.org/GNOME/nautilus.git\""],
+ [95, 95, " }"],
+ [96, 96, " ]"],
+ [97, 97, " }"],
+ [98, 98, " ]"]]
+ end
+
+ it 'returns the extracted blob lines correctly' do
+ extracted_lines = subject.blob_lines
+
+ expect(extracted_lines.size).to eq(5)
+
+ extracted_lines.each_with_index do |line, i|
+ expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
+ end
+ end
+ end
+
+ context 'diff lines' do
+ let(:expected_diff_lines) do
+ [[7, 7, "@@ -7,9 +7,6 @@"],
+ [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
+ [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
+ [9, 9, " \"finish-args\": ["],
+ [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
+ [11, 10, "- \"--socket=wayland\","],
+ [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
+ [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
+ [14, 11, " \"--filesystem=home\","],
+ [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
+ [62, 59, "@@ -62,7 +59,7 @@"],
+ [62, 59, " },"],
+ [63, 60, " {"],
+ [64, 61, " \"name\": \"gnome-desktop\","],
+ [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
+ [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
+ [66, 63, " \"sources\": ["],
+ [67, 64, " {"],
+ [68, 65, " \"type\": \"git\","],
+ [83, 80, "@@ -83,11 +80,6 @@"],
+ [83, 80, " \"buildsystem\": \"meson\","],
+ [84, 81, " \"builddir\": true,"],
+ [85, 82, " \"name\": \"nautilus\","],
+ [86, 83, "- \"config-opts\": ["],
+ [87, 83, "- \"-Denable-desktop=false\","],
+ [88, 83, "- \"-Denable-selinux=false\","],
+ [89, 83, "- \"--libdir=/app/lib\""],
+ [90, 83, "- ],"],
+ [91, 83, " \"sources\": ["],
+ [92, 84, " {"],
+ [93, 85, " \"type\": \"git\","],
+ # No new match line
+
+ # Injected blob lines
+ [94, 86, " \"url\": \"https://gitlab.gnome.org/GNOME/nautilus.git\""],
+ [95, 87, " }"],
+ [96, 88, " ]"],
+ [97, 89, " }"],
+ [98, 90, " ]"]]
+ # end
+ end
+
+ it 'return merge of blob lines with diff lines correctly' do
+ new_diff_lines = subject.unfolded_diff_lines
+
+ expected_diff_lines.each_with_index do |expected_line, i|
+ line = new_diff_lines[i]
+
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
+ end
+ end
+
+ it 'merged lines have correct line codes' do
+ new_diff_lines = subject.unfolded_diff_lines
+
+ new_diff_lines.each_with_index do |line, i|
+ old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+
+ unless line.type == 'match'
+ expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index 7eac2cacb90..01d43ed00a2 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -19,15 +19,6 @@ describe Gitlab::HashedStorage::Migrator do
end
end
- it 'sets projects as read only' do
- allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice
- subject.bulk_migrate(ids.min, ids.max)
-
- projects.each do |project|
- expect(project.reload.repository_read_only?).to be_truthy
- end
- end
-
it 'rescues and log exceptions' do
allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
expect { subject.bulk_migrate(ids.min, ids.max) }.not_to raise_error
@@ -40,6 +31,16 @@ describe Gitlab::HashedStorage::Migrator do
subject.bulk_migrate(ids.min, ids.max)
end
+
+ it 'has migrated projects set as writable' do
+ perform_enqueued_jobs do
+ subject.bulk_migrate(ids.min, ids.max)
+ end
+
+ projects.each do |project|
+ expect(project.reload.repository_read_only?).to be_falsey
+ end
+ end
end
describe '#migrate' do
@@ -57,19 +58,20 @@ describe Gitlab::HashedStorage::Migrator do
expect { subject.migrate(project) }.not_to raise_error
end
- it 'sets project as read only' do
- allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async)
- subject.migrate(project)
+ it 'migrate project' do
+ perform_enqueued_jobs do
+ subject.migrate(project)
+ end
- expect(project.reload.repository_read_only?).to be_truthy
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
end
- it 'migrate project' do
+ it 'has migrated project set as writable' do
perform_enqueued_jobs do
subject.migrate(project)
end
- expect(project.reload.hashed_storage?(:attachments)).to be_truthy
+ expect(project.reload.repository_read_only?).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index d3cae137c3c..5afa9669b1a 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -314,6 +314,7 @@ project:
- repository_languages
- pool_repository
- kubernetes_namespaces
+- error_tracking_setting
award_emoji:
- awardable
- user
@@ -345,3 +346,5 @@ resource_label_events:
- merge_request
- epic
- label
+error_tracking_setting:
+- project
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 2422868474e..fe2087e8fc3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -600,3 +600,8 @@ ResourceLabelEvent:
- label_id
- user_id
- created_at
+ErrorTracking::ProjectErrorTrackingSetting:
+- id
+- api_url
+- enabled
+- project_id
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index 1b9a8b4ab0d..741ee12633f 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -4,14 +4,14 @@ describe Gitlab::RepositoryCache do
let(:backend) { double('backend').as_null_object }
let(:project) { create(:project) }
let(:repository) { project.repository }
- let(:namespace) { "project:#{project.id}" }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
let(:cache) { described_class.new(repository, backend: backend) }
describe '#cache_key' do
subject { cache.cache_key(:foo) }
it 'includes the namespace' do
- expect(subject).to eq "#{namespace}:foo"
+ expect(subject).to eq "foo:#{namespace}"
end
context 'with a given namespace' do
@@ -22,7 +22,7 @@ describe Gitlab::RepositoryCache do
end
it 'includes the full namespace' do
- expect(subject).to eq "#{namespace}:#{extra_namespace}:foo"
+ expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
end
end
end
@@ -30,21 +30,21 @@ describe Gitlab::RepositoryCache do
describe '#expire' do
it 'expires the given key from the cache' do
cache.expire(:foo)
- expect(backend).to have_received(:delete).with("#{namespace}:foo")
+ expect(backend).to have_received(:delete).with("foo:#{namespace}")
end
end
describe '#fetch' do
it 'fetches the given key from the cache' do
cache.fetch(:bar)
- expect(backend).to have_received(:fetch).with("#{namespace}:bar")
+ expect(backend).to have_received(:fetch).with("bar:#{namespace}")
end
it 'accepts a block' do
p = -> {}
cache.fetch(:baz, &p)
- expect(backend).to have_received(:fetch).with("#{namespace}:baz", &p)
+ expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
end
end
@@ -67,7 +67,7 @@ describe Gitlab::RepositoryCache do
end
it 'caches the value' do
- expect(backend).to receive(:write).with("#{namespace}:#{key}", true)
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", true)
cache.fetch_without_caching_false(key) { true }
end
@@ -83,7 +83,7 @@ describe Gitlab::RepositoryCache do
end
it 'does not cache the value' do
- expect(backend).not_to receive(:write).with("#{namespace}:#{key}", true)
+ expect(backend).not_to receive(:write).with("#{key}:#{namespace}", true)
cache.fetch_without_caching_false(key, &p)
end
@@ -92,7 +92,7 @@ describe Gitlab::RepositoryCache do
context 'when the cached value is truthy' do
before do
- backend.write("#{namespace}:#{key}", true)
+ backend.write("#{key}:#{namespace}", true)
end
it 'returns the cached value' do
@@ -116,7 +116,7 @@ describe Gitlab::RepositoryCache do
context 'when the cached value is falsey' do
before do
- backend.write("#{namespace}:#{key}", false)
+ backend.write("#{key}:#{namespace}", false)
end
it 'returns the result of the block' do
@@ -126,7 +126,7 @@ describe Gitlab::RepositoryCache do
end
it 'writes the truthy value to the cache' do
- expect(backend).to receive(:write).with("#{namespace}:#{key}", 'block result')
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", 'block result')
cache.fetch_without_caching_false(key) { 'block result' }
end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index 8a28ad0e597..fd443cc1f71 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::RequestContext do
let(:ip) { '192.168.1.11' }
before do
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
described_class.new(app).call(env)
end
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index c2e2db27362..c1eaf0bb0bf 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -25,6 +25,8 @@ describe OmniAuth::Strategies::Jwt do
subject.options[:secret] = secret
subject.options[:algorithm] = algorithm
+ # We use Rack::Request instead of ActionDispatch::Request because
+ # Rack::Test::Methods enables testing of this module.
expect_next_instance_of(Rack::Request) do |rack_request|
expect(rack_request).to receive(:params).and_return('jwt' => payload)
end
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
new file mode 100644
index 00000000000..09253cf8003
--- /dev/null
+++ b/spec/mailers/emails/issues_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+describe Emails::Issues do
+ include EmailSpec::Matchers
+
+ describe "#import_issues_csv_email" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ subject { Notify.import_issues_csv_email(user.id, project.id, @results) }
+
+ it "shows number of successful issues imported" do
+ @results = { success: 165, error_lines: [], parse_error: false }
+
+ expect(subject).to have_body_text "165 issues imported"
+ end
+
+ it "shows error when file is invalid" do
+ @results = { success: 0, error_lines: [], parse_error: true }
+
+ expect(subject).to have_body_text "Error parsing CSV"
+ end
+
+ it "shows line numbers with errors" do
+ @results = { success: 0, error_lines: [23, 34, 58], parse_error: false }
+
+ expect(subject).to have_body_text "23, 34, 58"
+ end
+ end
+end
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index 066fe7d154e..005005b236b 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe CommitCollection do
let(:project) { create(:project, :repository) }
- let(:commit) { project.commit }
+ let(:commit) { project.commit("c1c67abbaf91f624347bb3ae96eabe3a1b742478") }
describe '#each' do
it 'yields every commit' do
@@ -12,6 +12,29 @@ describe CommitCollection do
end
end
+ describe '.committers' do
+ it 'returns a relation of users when users are found' do
+ user = create(:user, email: commit.committer_email.upcase)
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to contain_exactly(user)
+ end
+
+ it 'returns empty array when committers cannot be found' do
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to be_empty
+ end
+
+ it 'excludes authors of merge commits' do
+ commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
+ create(:user, email: commit.committer_email.upcase)
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to be_empty
+ end
+ end
+
describe '#with_pipeline_status' do
it 'sets the pipeline status for every commit so no additional queries are necessary' do
create(
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
new file mode 100644
index 00000000000..83f29718eda
--- /dev/null
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ErrorTracking::ProjectErrorTrackingSetting do
+ set(:project) { create(:project) }
+
+ describe 'Associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'Validations' do
+ subject { create(:project_error_tracking_setting, project: project) }
+
+ context 'when api_url is over 255 chars' do
+ before do
+ subject.api_url = 'https://' + 'a' * 250
+ end
+
+ it 'fails validation' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include('is too long (maximum is 255 characters)')
+ end
+ end
+
+ context 'With unsafe url' do
+ let(:project_error_tracking_setting) { create(:project_error_tracking_setting, project: project) }
+
+ it 'fails validation' do
+ project_error_tracking_setting.api_url = "https://replaceme.com/'><script>alert(document.cookie)</script>"
+
+ expect(project_error_tracking_setting).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 96d49e86dab..e18b29df321 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -991,6 +991,34 @@ describe MergeRequest do
end
end
+ describe '#committers' do
+ it 'returns all the committers of every commit in the merge request' do
+ users = subject.commits.map(&:committer_email).uniq.map do |email|
+ create(:user, email: email)
+ end
+
+ expect(subject.committers).to match_array(users)
+ end
+
+ it 'returns an empty array if no committer is associated with a user' do
+ expect(subject.committers).to be_empty
+ end
+ end
+
+ describe '#authors' do
+ it 'returns a list with all the committers in the merge request and author' do
+ users = subject.commits.map(&:committer_email).uniq.map do |email|
+ create(:user, email: email)
+ end
+
+ expect(subject.authors).to match_array([subject.author, *users])
+ end
+
+ it 'returns only the author if no committer is associated with a user' do
+ expect(subject.authors).to contain_exactly(subject.author)
+ end
+ end
+
describe '#hook_attrs' do
it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do
builder = double
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bcdfe3cf1eb..385b8a7959f 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -890,4 +890,19 @@ describe Note do
end
end
end
+
+ describe '#parent' do
+ it 'returns project for project notes' do
+ project = create(:project)
+ note = create(:note_on_issue, project: project)
+
+ expect(note.parent).to eq(project)
+ end
+
+ it 'returns nil for personal snippet note' do
+ note = create(:note_on_personal_snippet)
+
+ expect(note.parent).to be_nil
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 98bed4df3ee..3044150bca8 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -62,6 +62,7 @@ describe Project do
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
+ it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:ci_pipelines) }
it { is_expected.to have_many(:builds) }
@@ -2448,6 +2449,20 @@ describe Project do
end
end
+ describe '#set_repository_read_only!' do
+ let(:project) { create(:project) }
+
+ it 'returns true when there is no existing git transfer in progress' do
+ expect(project.set_repository_read_only!).to be_truthy
+ end
+
+ it 'returns false when there is an existing git transfer in progress' do
+ allow(project).to receive(:git_transfer_in_progress?) { true }
+
+ expect(project.set_repository_read_only!).to be_falsey
+ end
+ end
+
describe '#pushes_since_gc' do
let(:project) { create(:project) }
@@ -3181,6 +3196,33 @@ describe Project do
end
end
+ describe '#git_transfer_in_progress?' do
+ let(:project) { build(:project) }
+
+ subject { project.git_transfer_in_progress? }
+
+ it 'returns false when repo_reference_count and wiki_reference_count are 0' do
+ allow(project).to receive(:repo_reference_count) { 0 }
+ allow(project).to receive(:wiki_reference_count) { 0 }
+
+ expect(subject).to be_falsey
+ end
+
+ it 'returns true when repo_reference_count is > 0' do
+ allow(project).to receive(:repo_reference_count) { 2 }
+ allow(project).to receive(:wiki_reference_count) { 0 }
+
+ expect(subject).to be_truthy
+ end
+
+ it 'returns true when wiki_reference_count is > 0' do
+ allow(project).to receive(:repo_reference_count) { 0 }
+ allow(project).to receive(:wiki_reference_count) { 2 }
+
+ expect(subject).to be_truthy
+ end
+ end
+
context 'legacy storage' do
let(:project) { create(:project, :repository, :legacy_storage) }
let(:gitlab_shell) { Gitlab::Shell.new }
@@ -3241,10 +3283,6 @@ describe Project do
expect(project.migrate_to_hashed_storage!).to be_truthy
end
- it 'flags as read-only' do
- expect { project.migrate_to_hashed_storage! }.to change { project.repository_read_only }.to(true)
- end
-
it 'does not validate project visibility' do
expect(project).not_to receive(:visibility_level_allowed_as_fork)
expect(project).not_to receive(:visibility_level_allowed_by_group)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 2063b4bbe75..ac5874fd0f7 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2400,22 +2400,4 @@ describe Repository do
repository.merge_base('master', 'fix')
end
end
-
- describe '#cache' do
- subject(:cache) { repository.send(:cache) }
-
- it 'returns a RepositoryCache' do
- expect(subject).to be_kind_of Gitlab::RepositoryCache
- end
-
- it 'when is_wiki it includes wiki as part of key' do
- allow(repository).to receive(:is_wiki) { true }
-
- expect(subject.namespace).to include('wiki')
- end
-
- it 'when is_wiki is false extra_namespace is nil' do
- expect(subject.namespace).not_to include('wiki')
- end
- end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dd40f3d1561..79c0a1953dc 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1189,13 +1189,13 @@ describe API::MergeRequests do
end
it 'returns 404 if the merge request is not found' do
- post api("/projects/#{project.id}/merge_requests/123/merge_when_pipeline_succeeds", user)
+ post api("/projects/#{project.id}/merge_requests/123/cancel_merge_when_pipeline_succeeds", user)
expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge_when_pipeline_succeeds", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/cancel_merge_when_pipeline_succeeds", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 939e870ec53..5b625fd47be 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -387,7 +387,7 @@ describe 'Git HTTP requests' do
it "responds with status 401" do
expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return('1.2.3.4')
clone_get(path, env)
@@ -548,7 +548,7 @@ describe 'Git HTTP requests' do
maxretry = options[:maxretry] - 1
ip = '1.2.3.4'
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
Rack::Attack::Allow2Ban.reset(ip, options)
maxretry.times.each do
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
new file mode 100644
index 00000000000..516a1137319
--- /dev/null
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Issues::ImportCsvService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ subject do
+ uploader = FileUploader.new(project)
+ uploader.store!(file)
+
+ described_class.new(user, project, uploader).execute
+ end
+
+ describe '#execute' do
+ context 'invalid file' do
+ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
+
+ it 'returns invalid file error' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(0)
+ expect(subject[:parse_error]).to eq(true)
+ end
+ end
+
+ context 'comma delimited file' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
+
+ it 'imports CSV without errors' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+
+ context 'tab delimited file with error row' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_tab.csv') }
+
+ it 'imports CSV with some error rows' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(2)
+ expect(subject[:error_lines]).to eq([3])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+
+ context 'semicolon delimited file with CRLF' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_semicolon.csv') }
+
+ it 'imports CSV with a blank row' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([4])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 1d9c75dedce..1169ed5f9f2 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -509,7 +509,7 @@ describe MergeRequests::RefreshService do
committed_date: Time.now
)
- allow_any_instance_of(MergeRequest).to receive(:commits).and_return([commit])
+ allow_any_instance_of(MergeRequest).to receive(:commits).and_return(CommitCollection.new(@project, [commit], 'feature'))
end
context 'when the merge request is sourced from the same project' do
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 0e82194e9ee..b720f37ffdb 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -15,6 +15,20 @@ describe Projects::HashedStorage::MigrateRepositoryService 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::RepositoryMigrationError)
+ end
+ end
+
context 'when succeeds' do
it 'renames project and wiki repositories' do
service.execute
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index 332c1600cde..731be907453 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -11,6 +11,67 @@ describe Projects::Operations::UpdateService do
subject { described_class.new(project, user, params) }
describe '#execute' do
+ context 'error tracking' do
+ context 'with existing error tracking setting' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: false,
+ api_url: 'http://url',
+ token: 'token'
+ }
+ }
+ end
+
+ before do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'updates the settings' 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 eq('http://url')
+ expect(project.error_tracking_setting.token).to eq('token')
+ end
+ end
+
+ context 'without an existing error tracking setting' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: true,
+ api_url: 'http://url',
+ token: 'token'
+ }
+ }
+ end
+
+ it 'creates a setting' 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://url')
+ expect(project.error_tracking_setting.token).to eq('token')
+ end
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { {} }
+
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'does nothing' do
+ expect(result[:status]).to eq(:success)
+ expect(project.reload.error_tracking_setting)
+ .to eq(error_tracking_setting)
+ end
+ end
+ end
+
context 'with inappropriate params' do
let(:params) { { name: '' } }
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 9b232a52efa..4a809d5bf18 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -63,11 +63,11 @@ describe UploadService do
@link_to_file = upload_file(@project, txt)
end
- it { expect(@link_to_file).to eq(nil) }
+ it { expect(@link_to_file).to eq({}) }
end
end
def upload_file(project, file)
- described_class.new(project, file, FileUploader).execute
+ described_class.new(project, file, FileUploader).execute.to_h
end
end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index f3f3386382f..1bb42382e8a 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -172,4 +172,55 @@ describe UrlValidator do
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/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index b0042be339c..d9bda1a3414 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -32,11 +32,7 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:noteable, closed_merge_request)
assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none)
- assign(
- :issuable_sidebar,
- MergeRequestSerializer.new(current_user: user, project: project)
- .represent(closed_merge_request, serializer: 'sidebar')
- )
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
preload_view_requirements
@@ -45,6 +41,33 @@ describe 'projects/merge_requests/show.html.haml' do
current_application_settings: Gitlab::CurrentSettings.current_application_settings)
end
+ describe 'merge request assignee sidebar' do
+ context 'when assignee is allowed to merge' do
+ it 'does not show a warning icon' do
+ closed_merge_request.update(assignee_id: user.id)
+ project.add_maintainer(user)
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
+
+ render
+
+ 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
it 'shows the "Reopen" button' do
render
@@ -80,4 +103,10 @@ describe 'projects/merge_requests/show.html.haml' do
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
end
+
+ def serialize_issuable_sidebar(user, project, merge_request)
+ MergeRequestSerializer
+ .new(current_user: user, project: project)
+ .represent(closed_merge_request, serializer: 'sidebar')
+ 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
new file mode 100644
index 00000000000..752fd82c5e8
--- /dev/null
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rails_helper'
+
+describe 'projects/settings/operations/show' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ assign :project, project
+ end
+
+ describe 'Operations > Error Tracking' do
+ before do
+ stub_feature_flags(error_tracking: true)
+
+ project.add_reporter(user)
+
+ allow(view).to receive(:error_tracking_setting)
+ .and_return(error_tracking_setting)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ context 'Settings page ' do
+ it 'renders the Operations Settings page' do
+ render
+
+ 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
+end
diff --git a/spec/workers/import_issues_csv_worker_spec.rb b/spec/workers/import_issues_csv_worker_spec.rb
new file mode 100644
index 00000000000..89370c4890d
--- /dev/null
+++ b/spec/workers/import_issues_csv_worker_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ImportIssuesCsvWorker do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:upload) { create(:upload) }
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'calls #execute on Issues::ImportCsvService and destroys upload' do
+ expect_any_instance_of(Issues::ImportCsvService).to receive(:execute).and_return({ success: 5, errors: [], valid_file: true })
+
+ worker.perform(user.id, project.id, upload.id)
+
+ expect { upload.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 1068d4a68ef..7bf59f9de94 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -641,10 +641,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.47.0.tgz#c03dda41aefd3889cbfed95a391836106ae2ac4d"
integrity sha512-0Bx/HxqR8xpqqaLnZiFAHIh1jTAFQPFToVZ6Wi3QyhsAwmXRAbgw1SlkRMZ7w3e6l+G71Wnw+GnI4rx1gK8JLQ==
-"@gitlab/ui@^1.18.0":
- version "1.18.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.18.0.tgz#5cc591b2c7958e59fa7b1b443d4235e0e8f956c9"
- integrity sha512-JqmiRSGYmK0DbGBQJBpjeRrcgjK25rCqG6QW6/GPTVLtRjbPPZYGvVg5PyA6nJUGAnwFoeApUZVML6X3OpnV1Q==
+"@gitlab/ui@^1.20.0":
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.20.0.tgz#50bd4b092646a2c6337f0f462779af8e702dda05"
+ integrity sha512-EJgrqon/tYCUPoOgnNNAXbrDXOEAajJwKHr4aR2R6vkJI3kVZiq66RNIe5ftGIUoNqYCDnRIkpLyo7MqzJPgcw==
dependencies:
babel-standalone "^6.26.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -8737,10 +8737,10 @@ slugify@^1.3.1:
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2"
integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw==
-smooshpack@^0.0.53:
- version "0.0.53"
- resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.53.tgz#aa397ca43619912e8ac0aa32012846ff4feaa5e8"
- integrity sha512-FVXvKvZOz5+Tk/zUJ/wxM+ftu1yZtFEmeQl4chCqbzK/reU0L/OdDiYpx+/27Jt2VX09j08oIzwoyQ5fHH4+WQ==
+smooshpack@^0.0.54:
+ version "0.0.54"
+ resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.54.tgz#9044358b85052d348b801f385678c8a0c76f2bb6"
+ integrity sha512-yIwEWb17hqoW5IaWyzO6O6nxY89I5UdRoGIZy5hihoqXP9OYcoMbBTxKwS57MeXSKdNA2rtk86rlCcOgAYIgrA==
dependencies:
codesandbox-api "^0.0.20"
codesandbox-import-utils "^1.2.3"