Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/api.js2
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue1
-rw-r--r--app/assets/javascripts/profile/profile.js1
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/mixins.scss5
-rw-r--r--app/assets/stylesheets/pages/diff.scss22
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss10
-rw-r--r--app/assets/stylesheets/pages/notifications.scss28
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/import/phabricator_controller.rb35
-rw-r--r--app/controllers/profiles/accounts_controller.rb2
-rw-r--r--app/controllers/profiles/groups_controller.rb24
-rw-r--r--app/controllers/profiles/passwords_controller.rb10
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb6
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/members.rb3
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb8
-rw-r--r--app/mailers/emails/projects.rb10
-rw-r--r--app/mailers/emails/remote_mirrors.rb2
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/models/ci/build.rb64
-rw-r--r--app/models/concerns/artifact_migratable.rb58
-rw-r--r--app/models/concerns/referable.rb1
-rw-r--r--app/models/group.rb14
-rw-r--r--app/models/milestone.rb11
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/notification_recipient.rb22
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/uploaders/legacy_artifact_uploader.rb27
-rw-r--r--app/views/import/gitlab_projects/new.html.haml23
-rw-r--r--app/views/import/manifest/new.html.haml6
-rw-r--r--app/views/import/phabricator/new.html.haml25
-rw-r--r--app/views/import/shared/_errors.html.haml4
-rw-r--r--app/views/import/shared/_new_project_form.html.haml21
-rw-r--r--app/views/profiles/emails/index.html.haml6
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml23
-rw-r--r--app/views/profiles/notifications/show.html.haml5
-rw-r--r--app/views/profiles/passwords/edit.html.haml25
-rw-r--r--app/views/profiles/passwords/new.html.haml6
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml10
-rw-r--r--app/views/profiles/two_factor_auths/codes.html.haml5
-rw-r--r--app/views/profiles/two_factor_auths/create.html.haml4
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml61
-rw-r--r--app/views/projects/_import_project_pane.html.haml7
-rw-r--r--app/views/shared/_old_visibility_level.html.haml2
-rw-r--r--app/views/shared/notifications/_button.html.haml12
-rw-r--r--changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml5
-rw-r--r--changelogs/unreleased/59587-add-graphql-logging.yml5
-rw-r--r--changelogs/unreleased/60987-emoji-picker-popup.yml5
-rw-r--r--changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml5
-rw-r--r--changelogs/unreleased/62432-fix-participants-wrapping.yml5
-rw-r--r--changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml5
-rw-r--r--changelogs/unreleased/add-constraint-for-milestone-dates.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.43.0.yml5
-rw-r--r--changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml5
-rw-r--r--changelogs/unreleased/remove-legacy-artifacts-related-code.yml5
-rw-r--r--changelogs/unreleased/weimeng-email-routing.yml5
-rw-r--r--config/routes/import.rb2
-rw-r--r--config/routes/profile.rb6
-rw-r--r--config/routes/project.rb83
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--db/migrate/20190327163904_add_notification_email_to_notification_settings.rb11
-rw-r--r--db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb24
-rw-r--r--db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb38
-rw-r--r--db/migrate/20190524062810_generate_lets_encrypt_private_key.rb17
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/logs.md14
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/analytics/README.md4
-rw-r--r--doc/analytics/contribution_analytics.md4
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ee/index.md4
-rw-r--r--doc/customization/issue_and_merge_request_template.md4
-rw-r--r--doc/development/ee_features.md9
-rw-r--r--doc/development/testing_guide/end_to_end/best_practices.md (renamed from qa/docs/best_practices.md)2
-rw-r--r--doc/development/testing_guide/end_to_end/index.md (renamed from doc/development/testing_guide/end_to_end_tests.md)10
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md (renamed from qa/qa/page/README.md)0
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md (renamed from qa/docs/writing_tests_from_scratch.md)17
-rw-r--r--doc/development/testing_guide/end_to_end/resources.md (renamed from qa/qa/resource/README.md)6
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md (renamed from qa/docs/guidelines.md)12
-rw-r--r--doc/development/testing_guide/index.md2
-rw-r--r--doc/development/testing_guide/smoke.md2
-rw-r--r--doc/development/testing_guide/testing_levels.md2
-rw-r--r--doc/git_hooks/git_hooks.md4
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/install/aws/index.md8
-rw-r--r--doc/install/kubernetes/index.md2
-rw-r--r--doc/install/requirements.md8
-rw-r--r--doc/integration/elasticsearch.md4
-rw-r--r--doc/integration/saml.md2
-rw-r--r--doc/integration/slash_commands.md4
-rw-r--r--doc/license/README.md4
-rw-r--r--doc/push_rules/push_rules.md2
-rw-r--r--doc/subscriptions/index.md2
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/user/admin_area/geo_nodes.md4
-rw-r--r--doc/user/admin_area/index.md2
-rw-r--r--doc/user/admin_area/settings/email.md2
-rw-r--r--doc/user/application_security/container_scanning/index.md4
-rw-r--r--doc/user/group/clusters/index.md2
-rw-r--r--doc/user/group/index.md6
-rw-r--r--doc/user/group/insights/index.md2
-rw-r--r--doc/user/group/saml_sso/scim_setup.md2
-rw-r--r--doc/user/group/security_dashboard/index.md4
-rw-r--r--doc/user/index.md26
-rw-r--r--doc/user/permissions.md261
-rw-r--r--doc/user/profile/personal_access_tokens.md18
-rw-r--r--doc/user/project/ci_cd_for_external_repo.md4
-rw-r--r--doc/user/project/clusters/index.md6
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md6
-rw-r--r--doc/user/project/clusters/runbooks/index.md6
-rw-r--r--doc/user/project/deploy_tokens/index.md2
-rw-r--r--doc/user/project/import/gemnasium.md10
-rw-r--r--doc/user/project/import/github.md4
-rw-r--r--doc/user/project/import/index.md3
-rw-r--r--doc/user/project/import/phabricator.md32
-rw-r--r--doc/user/project/index.md12
-rw-r--r--doc/user/project/insights/index.md4
-rw-r--r--doc/user/project/integrations/project_services.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md4
-rw-r--r--doc/user/project/issues/create_new_issue.md2
-rw-r--r--doc/user/project/issues/index.md6
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md4
-rw-r--r--doc/user/project/issues/related_issues.md11
-rw-r--r--doc/user/project/maven_packages.md4
-rw-r--r--doc/user/project/merge_requests/code_quality_diff.md2
-rw-r--r--doc/user/project/merge_requests/container_scanning.md4
-rw-r--r--doc/user/project/merge_requests/dast.md4
-rw-r--r--doc/user/project/merge_requests/dependency_scanning.md4
-rw-r--r--doc/user/project/merge_requests/index.md18
-rw-r--r--doc/user/project/merge_requests/license_management.md4
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md4
-rw-r--r--doc/user/project/merge_requests/sast.md4
-rw-r--r--doc/user/project/merge_requests/sast_docker.md4
-rw-r--r--doc/user/project/milestones/burndown_charts.md2
-rw-r--r--doc/user/project/packages/maven_repository.md2
-rw-r--r--doc/user/project/packages/npm_registry.md2
-rw-r--r--doc/user/project/repository/index.md6
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md9
-rw-r--r--doc/user/project/security_dashboard.md4
-rw-r--r--doc/user/project/settings/index.md4
-rw-r--r--doc/user/search/advanced_global_search.md2
-rw-r--r--doc/user/search/advanced_search_syntax.md2
-rw-r--r--doc/workflow/img/notification_global_settings.pngbin37542 -> 118914 bytes
-rw-r--r--doc/workflow/notifications.md8
-rw-r--r--lib/api/commits.rb10
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/gitlab/git/repository.rb28
-rw-r--r--lib/gitlab/github_import/parallel_importer.rb18
-rw-r--r--lib/gitlab/graphql/query_analyzers/log_query_complexity.rb18
-rw-r--r--lib/gitlab/graphql/query_analyzers/logger_analyzer.rb71
-rw-r--r--lib/gitlab/graphql_logger.rb9
-rw-r--r--lib/gitlab/http.rb7
-rw-r--r--lib/gitlab/import/set_async_jid.rb27
-rw-r--r--lib/gitlab/import_sources.rb3
-rw-r--r--lib/gitlab/lets_encrypt/client.rb23
-rw-r--r--lib/gitlab/phabricator_import.rb12
-rw-r--r--lib/gitlab/phabricator_import/base_worker.rb80
-rw-r--r--lib/gitlab/phabricator_import/cache/map.rb65
-rw-r--r--lib/gitlab/phabricator_import/conduit.rb9
-rw-r--r--lib/gitlab/phabricator_import/conduit/client.rb41
-rw-r--r--lib/gitlab/phabricator_import/conduit/maniphest.rb28
-rw-r--r--lib/gitlab/phabricator_import/conduit/pagination.rb24
-rw-r--r--lib/gitlab/phabricator_import/conduit/response.rb60
-rw-r--r--lib/gitlab/phabricator_import/conduit/tasks_response.rb24
-rw-r--r--lib/gitlab/phabricator_import/import_tasks_worker.rb10
-rw-r--r--lib/gitlab/phabricator_import/importer.rb44
-rw-r--r--lib/gitlab/phabricator_import/issues/importer.rb42
-rw-r--r--lib/gitlab/phabricator_import/issues/task_importer.rb54
-rw-r--r--lib/gitlab/phabricator_import/project_creator.rb78
-rw-r--r--lib/gitlab/phabricator_import/representation/task.rb60
-rw-r--r--lib/gitlab/phabricator_import/worker_state.rb47
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--locale/gitlab.pot173
-rw-r--r--qa/README.md12
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Dockerfile9
-rw-r--r--qa/qa/runtime/api/client.rb9
-rw-r--r--spec/controllers/import/phabricator_controller_spec.rb92
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb4
-rw-r--r--spec/factories/ci/builds.rb11
-rw-r--r--spec/factories/ci/job_artifacts.rb18
-rw-r--r--spec/features/commits_spec.rb6
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb6
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb10
-rw-r--r--spec/features/projects/pages_spec.rb15
-rw-r--r--spec/fixtures/phabricator_responses/auth_failed.json1
-rw-r--r--spec/fixtures/phabricator_responses/maniphest.search.json98
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb2
-rw-r--r--spec/javascripts/api_spec.js2
-rw-r--r--spec/javascripts/boards/mock_data.js8
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js1
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb52
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb11
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb25
-rw-r--r--spec/lib/gitlab/graphql_logger_spec.rb40
-rw-r--r--spec/lib/gitlab/import/set_async_jid_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb13
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb46
-rw-r--r--spec/lib/gitlab/phabricator_import/base_worker_spec.rb74
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb66
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/client_spec.rb59
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb39
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb79
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb27
-rw-r--r--spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb16
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb32
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb53
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb54
-rw-r--r--spec/lib/gitlab/phabricator_import/project_creator_spec.rb58
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb33
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb46
-rw-r--r--spec/lib/gitlab/shell_spec.rb10
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb10
-rw-r--r--spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb35
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb12
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb4
-rw-r--r--spec/models/ci/build_spec.rb146
-rw-r--r--spec/models/milestone_spec.rb49
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb92
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb25
-rw-r--r--spec/requests/api/graphql_spec.rb48
-rw-r--r--spec/requests/api/jobs_spec.rb4
-rw-r--r--spec/requests/api/runner_spec.rb10
-rw-r--r--spec/routing/import_routing_spec.rb12
-rw-r--r--spec/routing/project_routing_spec.rb19
-rw-r--r--spec/services/ci/retry_build_service_spec.rb5
-rw-r--r--spec/services/projects/update_pages_service_spec.rb59
-rw-r--r--spec/support/helpers/git_helpers.rb8
-rw-r--r--spec/support/matchers/eq_pem.rb11
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb45
-rw-r--r--spec/tasks/gitlab/artifacts/migrate_rake_spec.rb55
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb18
-rw-r--r--spec/tasks/tokens_spec.rb4
-rw-r--r--spec/uploaders/legacy_artifact_uploader_spec.rb61
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb34
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb6
252 files changed, 3289 insertions, 1354 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e640847f99c..b978278f05f 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.42.1
+1.43.0
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index e583a8affd4..7cebb88f3a4 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -12,7 +12,7 @@ const Api = {
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
- projectLabelsPath: '/:namespace_path/:project_path/labels',
+ projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 0c0a0faa59d..7cf3d90d468 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -86,7 +86,6 @@ export default {
:key="note.id"
:img-src="note.author.avatar_url"
:tooltip-text="getTooltipText(note)"
- :size="19"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="toggleDiscussions"
/>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 78f397ccc12..c9c40cb6acf 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -147,6 +147,7 @@ export default {
class="note-action-button note-emoji-button js-add-award js-note-emoji"
href="#"
title="Add reaction"
+ data-position="right"
>
<icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" />
<icon css-classes="link-highlight award-control-icon-positive" name="smiley" />
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index 6e3800021b4..8dd37aee7e1 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -39,6 +39,7 @@ export default class Profile {
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
+ $('.js-group-notification-email').on('change', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
this.form.on('submit', this.onSubmitForm);
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index f4df98ac2ff..cccde1bb278 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -100,7 +100,7 @@ export default {
this.fetchFiles();
}
})
- .catch(() => createFlash(__('An error occurding while fetching folder content.')));
+ .catch(() => createFlash(__('An error occurred while fetching folder content.')));
},
normalizeData(key, data) {
return this.entries[key].concat(data.map(({ node }) => node));
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index b85abfd9c14..97a763671ba 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,12 +1,12 @@
@mixin btn-comment-icon {
border-radius: 50%;
background: $white-light;
- padding: 1px 5px;
+ padding: 1px;
font-size: 12px;
color: $blue-500;
+ border: 1px solid $blue-500;
width: 24px;
height: 24px;
- border: 1px solid $blue-500;
&:hover,
&.inverted {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 18671f7c4d8..df40149f0a6 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -385,3 +385,8 @@
height: $size;
margin-right: $margin-right;
}
+
+@mixin code-icon-size() {
+ width: $gl-font-size * $code-line-height * 0.9;
+ height: $gl-font-size * $code-line-height * 0.9;
+}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b3a634e23a3..4a648a5d3b6 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -615,10 +615,9 @@ table.code {
.diff-comment-avatar-holders {
position: absolute;
- height: 19px;
- width: 19px;
- margin-left: -15px;
+ margin-left: -$gl-padding;
z-index: 100;
+ @include code-icon-size();
&:hover {
.diff-comment-avatar,
@@ -652,26 +651,28 @@ table.code {
.diff-comments-more-count {
position: absolute;
left: 0;
- width: 19px;
- height: 19px;
margin-right: 0;
border-color: $white-light;
cursor: pointer;
transition: all 0.1s ease-out;
+ @include code-icon-size();
@for $i from 1 through 4 {
&:nth-child(#{$i}) {
z-index: (4 - $i);
}
}
+
+ .avatar {
+ @include code-icon-size();
+ }
}
.diff-comments-more-count {
- width: 19px;
- min-width: 19px;
padding-left: 0;
padding-right: 0;
overflow: hidden;
+ @include code-icon-size();
}
.diff-comments-more-count,
@@ -680,12 +681,15 @@ table.code {
}
.diff-notes-collapse {
- width: 24px;
- height: 24px;
+ border: 0;
border-radius: 50%;
padding: 0;
transition: transform 0.1s ease-out;
z-index: 100;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ @include code-icon-size();
.collapse-icon {
height: 50%;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 04c66006027..4ba74d34664 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -218,7 +218,7 @@
.title {
color: $gl-text-color;
- margin-bottom: 10px;
+ margin-bottom: $gl-padding-8;
line-height: 1;
.avatar {
@@ -604,7 +604,6 @@
.participants-list {
display: flex;
flex-wrap: wrap;
- margin: -7px;
}
.user-list {
@@ -614,7 +613,7 @@
.participants-author {
display: inline-block;
- padding: 7px;
+ padding: 0 $gl-padding-8 $gl-padding-8 0;
&:nth-of-type(7n) {
padding-right: 0;
@@ -641,7 +640,6 @@
.participants-more,
.user-list-more {
- margin-top: 5px;
margin-left: 5px;
a,
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 170432a9e62..32477c20db6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -437,7 +437,9 @@ $note-form-margin-left: 72px;
.diff-file {
.is-over {
.add-diff-note {
- display: inline-block;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
}
}
@@ -741,7 +743,7 @@ $note-form-margin-left: 72px;
.add-diff-note {
@include btn-comment-icon;
opacity: 0;
- margin-left: -50px;
+ margin-left: -52px;
position: absolute;
top: 50%;
transform: translateY(-50%);
@@ -900,10 +902,6 @@ $note-form-margin-left: 72px;
.diff-comment-form {
display: block;
}
-
- .add-diff-note svg {
- margin-top: 4px;
- }
}
.discussion-filter-container {
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index e98cb444f0a..e1cbf0e6654 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -4,6 +4,34 @@
.dropdown-menu {
@extend .dropdown-menu-right;
}
+
+ @include media-breakpoint-down(sm) {
+ .notification-dropdown {
+ width: 100%;
+ }
+
+ .notification-form {
+ display: block;
+ }
+
+ .notifications-btn,
+ .btn-group {
+ width: 100%;
+ }
+
+ .table-section {
+ border-top: 0;
+ min-height: unset;
+
+ &:not(:first-child) {
+ padding-top: 0;
+ }
+ }
+
+ .update-notifications {
+ width: 100%;
+ }
+ }
}
.notification {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 4cbab6811bc..6e98d66d712 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -42,7 +42,7 @@ class ApplicationController < ActionController::Base
:bitbucket_server_import_enabled?,
:google_code_import_enabled?, :fogbugz_import_enabled?,
:git_import_enabled?, :gitlab_project_import_enabled?,
- :manifest_import_enabled?
+ :manifest_import_enabled?, :phabricator_import_enabled?
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data.
@@ -424,6 +424,10 @@ class ApplicationController < ActionController::Base
Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end
+ def phabricator_import_enabled?
+ Gitlab::PhabricatorImport.available?
+ end
+
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
diff --git a/app/controllers/import/phabricator_controller.rb b/app/controllers/import/phabricator_controller.rb
new file mode 100644
index 00000000000..d1c04817689
--- /dev/null
+++ b/app/controllers/import/phabricator_controller.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class Import::PhabricatorController < Import::BaseController
+ include ImportHelper
+
+ before_action :verify_import_enabled
+
+ def new
+ end
+
+ def create
+ @project = Gitlab::PhabricatorImport::ProjectCreator
+ .new(current_user, import_params).execute
+
+ if @project&.persisted?
+ redirect_to @project
+ else
+ @name = params[:name]
+ @path = params[:path]
+ @errors = @project&.errors&.full_messages || [_("Invalid import params")]
+
+ render :new
+ end
+ end
+
+ def verify_import_enabled
+ render_404 unless phabricator_import_enabled?
+ end
+
+ private
+
+ def import_params
+ params.permit(:path, :phabricator_server_url, :api_token, :name, :namespace_id)
+ end
+end
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index 0d2a6145d0e..b03f4b7435f 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -17,7 +17,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
if unlink_provider_allowed?(provider)
identity.destroy
else
- flash[:alert] = "You are not allowed to unlink your primary login account"
+ flash[:alert] = _("You are not allowed to unlink your primary login account")
end
redirect_to profile_account_path
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
new file mode 100644
index 00000000000..c755bcb718a
--- /dev/null
+++ b/app/controllers/profiles/groups_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class Profiles::GroupsController < Profiles::ApplicationController
+ include RoutableActions
+
+ def update
+ group = find_routable!(Group, params[:id])
+ notification_setting = current_user.notification_settings.find_by(source: group) # rubocop: disable CodeReuse/ActiveRecord
+
+ if notification_setting.update(update_params)
+ flash[:notice] = "Notification settings for #{group.name} saved"
+ else
+ flash[:alert] = "Failed to save new settings for #{group.name}"
+ end
+
+ redirect_back_or_default(default: profile_notifications_path)
+ end
+
+ private
+
+ def update_params
+ params.require(:notification_setting).permit(:notification_email)
+ end
+end
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 7038447581c..d2787c2e450 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -14,7 +14,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
def create
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to new_profile_password_path, alert: _('You must provide a valid current password')
return
end
@@ -29,7 +29,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
if result[:status] == :success
Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute
- redirect_to root_path, notice: 'Password successfully changed'
+ redirect_to root_path, notice: _('Password successfully changed')
else
render :new
end
@@ -45,14 +45,14 @@ class Profiles::PasswordsController < Profiles::ApplicationController
password_attributes[:password_automatically_set] = false
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to edit_profile_password_path, alert: _('You must provide a valid current password')
return
end
result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
- flash[:notice] = "Password was successfully updated. Please login with it"
+ flash[:notice] = _('Password was successfully updated. Please login with it')
redirect_to new_user_session_path
else
@user.reset
@@ -62,7 +62,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
def reset
current_user.send_reset_password_instructions
- redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions'
+ redirect_to edit_profile_password_path, notice: _('We sent you an email with reset password instructions')
end
private
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 83e14275a8b..95b9344c551 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -18,7 +18,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
two_factor_authentication_reason(
global: lambda do
flash.now[:alert] =
- s_('The global settings require you to enable Two-Factor Authentication for your account.')
+ _('The global settings require you to enable Two-Factor Authentication for your account.')
end,
group: lambda do |groups|
flash.now[:alert] = groups_notification(groups)
@@ -27,7 +27,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
unless two_factor_grace_period_expired?
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = flash.now[:alert] + s_(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
+ flash.now[:alert] = flash.now[:alert] + _(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
end
end
@@ -44,7 +44,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
render 'create'
else
- @error = s_('Invalid pin code')
+ @error = _('Invalid pin code')
@qr_code = build_qr_code
setup_u2f_registration
render 'show'
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index a63f45f231c..f8ad6bee21b 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -16,7 +16,7 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::Connections
use Gitlab::Graphql::GenericTracing
- query_analyzer Gitlab::Graphql::QueryAnalyzers::LogQueryComplexity.analyzer
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new
query(Types::QueryType)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 7af766c8544..a3f53ca8dd6 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -99,7 +99,7 @@ module GroupsHelper
end
def remove_group_message(group)
- _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
+ _("You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name }
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 2b046d17122..f3a3203f7ad 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -83,7 +83,7 @@ module Emails
@project = Project.find(project_id)
@results = results
- mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
+ mail(to: recipient(@user.id, @project.group), subject: subject('Imported issues')) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
@@ -103,7 +103,7 @@ module Emails
def issue_thread_options(sender_id, recipient_id, reason)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@issue.title} (##{@issue.iid})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 91dfdf58982..2bfa59774d7 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -58,9 +58,8 @@ module Emails
@member_source_type = member_source_type
@member_source = member_source_class.find(source_id)
@invite_email = invite_email
- inviter = User.find(created_by_id)
- mail(to: inviter.notification_email,
+ mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source),
subject: subject('Invitation declined'))
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index fc6ed695675..864f9e2975a 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -110,7 +110,7 @@ module Emails
def merge_request_thread_options(sender_id, recipient_id, reason = nil)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 1b3c1f9a8a9..70d296fe3b8 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -51,7 +51,7 @@ module Emails
def note_thread_options(recipient_id)
{
from: sender(@note.author_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @group),
subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})")
}
end
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index ce449237ef6..2d390666f65 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -7,7 +7,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been enabled")
)
end
@@ -17,7 +17,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been disabled")
)
end
@@ -27,7 +27,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("Verification succeeded for GitLab Pages domain '#{domain.domain}'")
)
end
@@ -37,7 +37,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'")
)
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 2500622caa7..f81f76f67f7 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -7,20 +7,20 @@ module Emails
@project = Project.find project_id
@target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
- mail(to: @user.notification_email,
+ mail(to: recipient(user_id, @project.group),
subject: subject("Project was moved"))
end
def project_was_exported_email(current_user, project)
@project = project
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, project.group),
subject: subject("Project was exported"))
end
def project_was_not_exported_email(current_user, project, errors)
@project = project
@errors = errors
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, @project.group),
subject: subject("Project export error"))
end
@@ -28,7 +28,7 @@ module Emails
@project = project
@user = user
- mail(to: user.notification_email, subject: subject("Project cleanup has completed"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup has completed"))
end
def repository_cleanup_failure_email(project, user, error)
@@ -36,7 +36,7 @@ module Emails
@user = user
@error = error
- mail(to: user.notification_email, subject: subject("Project cleanup failure"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup failure"))
end
def repository_push_email(project_id, opts = {})
diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb
index 2018eb7260b..2d8137843ec 100644
--- a/app/mailers/emails/remote_mirrors.rb
+++ b/app/mailers/emails/remote_mirrors.rb
@@ -6,7 +6,7 @@ module Emails
@remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
@project = @remote_mirror.project
- mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed'))
+ mail(to: recipient(recipient_id, @project.group), subject: subject('Remote mirror update failed'))
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0b740809f30..576caea4c10 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -73,12 +73,22 @@ class Notify < BaseMailer
# Look up a User by their ID and return their email address
#
- # recipient_id - User ID
+ # recipient_id - User ID
+ # notification_group - The parent group of the notification
#
# Returns a String containing the User's email address.
- def recipient(recipient_id)
+ def recipient(recipient_id, notification_group = nil)
@current_user = User.find(recipient_id)
- @current_user.notification_email
+ group_notification_email = nil
+
+ if notification_group
+ notification_settings = notification_group.notification_settings_for(@current_user, hierarchy_order: :asc)
+ group_notification_email = notification_settings.find { |n| n.notification_email.present? }&.notification_email
+ end
+
+ # Return group-specific email address if present, otherwise return global
+ # email address
+ group_notification_email || @current_user.notification_email
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 56786fae6ea..f743fca423a 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -2,7 +2,6 @@
module Ci
class Build < CommitStatus
- prepend ArtifactMigratable
include Ci::Processable
include Ci::Metadatable
include Ci::Contextable
@@ -21,6 +20,11 @@ module Ci
BuildArchivedError = Class.new(StandardError)
ignore_column :commands
+ ignore_column :artifacts_file
+ ignore_column :artifacts_metadata
+ ignore_column :artifacts_file_store
+ ignore_column :artifacts_metadata_store
+ ignore_column :artifacts_size
belongs_to :project, inverse_of: :builds
belongs_to :runner
@@ -83,13 +87,7 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts_archive, ->() do
- if Feature.enabled?(:ci_enable_legacy_artifacts)
- where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
- '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
- else
- where('EXISTS (?)',
- Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
- end
+ where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
scope :with_existing_job_artifacts, ->(query) do
@@ -111,8 +109,8 @@ module Ci
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
- scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
- scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+ scope :with_artifacts_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.archive.with_files_stored_locally) }
+ scope :with_archived_trace_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.trace.with_files_stored_locally) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
@@ -142,16 +140,10 @@ module Ci
scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
- ##
- # TODO: Remove these mounters when we remove :ci_enable_legacy_artifacts feature flag
- mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file
- mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata
-
acts_as_taggable
add_authentication_token_field :token, encrypted: :optional
- before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
before_destroy { unscoped_project }
@@ -159,8 +151,6 @@ module Ci
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
- update_project_statistics stat: :build_artifacts_size, attribute: :artifacts_size
-
class << self
# This is needed for url_for to work,
# as the controller is JobsController
@@ -542,6 +532,26 @@ module Ci
trace.exist?
end
+ def artifacts_file
+ job_artifacts_archive&.file
+ end
+
+ def artifacts_size
+ job_artifacts_archive&.size
+ end
+
+ def artifacts_metadata
+ job_artifacts_metadata&.file
+ end
+
+ def artifacts?
+ !artifacts_expired? && artifacts_file&.exists?
+ end
+
+ def artifacts_metadata?
+ artifacts? && artifacts_metadata&.exists?
+ end
+
def has_job_artifacts?
job_artifacts.any?
end
@@ -610,14 +620,12 @@ module Ci
# and use that for `ExpireBuildInstanceArtifactsWorker`?
def erase_erasable_artifacts!
job_artifacts.erasable.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
end
def erase(opts = {})
return false unless erasable?
job_artifacts.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
erase_trace!
update_erased!(opts[:erased_by])
end
@@ -655,10 +663,7 @@ module Ci
end
def artifacts_file_for_type(type)
- file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
- # TODO: to be removed once legacy artifacts is removed
- file ||= legacy_artifacts_file if type == :archive
- file
+ job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
end
def coverage_regex
@@ -784,13 +789,6 @@ module Ci
private
- def erase_old_artifacts!
- # TODO: To be removed once we get rid of ci_enable_legacy_artifacts feature flag
- remove_artifacts_file!
- remove_artifacts_metadata!
- save
- end
-
def successful_deployment_status
if deployment&.last?
:last
@@ -812,10 +810,6 @@ module Ci
job_artifacts.select { |artifact| artifact.file_type.in?(report_types) }
end
- def update_artifacts_size
- self.artifacts_size = legacy_artifacts_file&.size
- end
-
def erase_trace!
trace.erase!
end
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
deleted file mode 100644
index 7c9f579b480..00000000000
--- a/app/models/concerns/artifact_migratable.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-# Adapter class to unify the interface between mounted uploaders and the
-# Ci::Artifact model
-# Meant to be prepended so the interface can stay the same
-module ArtifactMigratable
- def artifacts_file
- job_artifacts_archive&.file || legacy_artifacts_file
- end
-
- def artifacts_metadata
- job_artifacts_metadata&.file || legacy_artifacts_metadata
- end
-
- def artifacts?
- !artifacts_expired? && artifacts_file&.exists?
- end
-
- def artifacts_metadata?
- artifacts? && artifacts_metadata.exists?
- end
-
- def artifacts_file_changed?
- job_artifacts_archive&.file_changed? || attribute_changed?(:artifacts_file)
- end
-
- def remove_artifacts_file!
- if job_artifacts_archive
- job_artifacts_archive.destroy
- else
- remove_legacy_artifacts_file!
- end
- end
-
- def remove_artifacts_metadata!
- if job_artifacts_metadata
- job_artifacts_metadata.destroy
- else
- remove_legacy_artifacts_metadata!
- end
- end
-
- def artifacts_size
- read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
- end
-
- def legacy_artifacts_file
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-
- def legacy_artifacts_metadata
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-end
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 58143a32fdc..4a506146de3 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -73,6 +73,7 @@ module Referable
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
+ (?:\/\-)?
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
diff --git a/app/models/group.rb b/app/models/group.rb
index 53331a19776..cdb4e6e87f6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -126,10 +126,20 @@ class Group < Namespace
# Overrides notification_settings has_many association
# This allows to apply notification settings from parent groups
# to child groups and projects.
- def notification_settings
+ def notification_settings(hierarchy_order: nil)
source_type = self.class.base_class.name
+ settings = NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
- NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
+ return settings unless hierarchy_order && self_and_ancestors_ids.length > 1
+
+ settings
+ .joins("LEFT JOIN (#{self_and_ancestors(hierarchy_order: hierarchy_order).to_sql}) AS ordered_groups ON notification_settings.source_id = ordered_groups.id")
+ .select('notification_settings.*, ordered_groups.depth AS depth')
+ .order("ordered_groups.depth #{hierarchy_order}")
+ end
+
+ def notification_settings_for(user, hierarchy_order: nil)
+ notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end
def to_reference(_from = nil, full: nil)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 787600569fa..37c129e843a 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -58,6 +58,7 @@ class Milestone < ApplicationRecord
validate :uniqueness_of_title, if: :title_changed?
validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
+ validate :dates_within_4_digits
strip_attributes :title
@@ -326,6 +327,16 @@ class Milestone < ApplicationRecord
end
end
+ def dates_within_4_digits
+ if start_date && start_date > Date.new(9999, 12, 31)
+ errors.add(:start_date, _("date must not be after 9999-12-31"))
+ end
+
+ if due_date && due_date > Date.new(9999, 12, 31)
+ errors.add(:due_date, _("date must not be after 9999-12-31"))
+ end
+ end
+
def issues_finder_params
{ project_id: project_id }
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index f7c31890198..3c270c7396a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -206,12 +206,12 @@ class Namespace < ApplicationRecord
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
- def self_and_ancestors
+ def self_and_ancestors(hierarchy_order: nil)
return self.class.where(id: id) unless parent_id
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
- .base_and_ancestors
+ .base_and_ancestors(hierarchy_order: hierarchy_order)
end
# Returns all the descendants of the current namespace.
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 6889e0d776b..6f057f79ef6 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -156,23 +156,11 @@ class NotificationRecipient
# Returns the notification_setting of the lowest group in hierarchy with non global level
def closest_non_global_group_notification_settting
return unless @group
- return if indexed_group_notification_settings.empty?
- notification_setting = nil
-
- @group.self_and_ancestors_ids.each do |id|
- notification_setting = indexed_group_notification_settings[id]
- break if notification_setting
- end
-
- notification_setting
- end
-
- def indexed_group_notification_settings
- strong_memoize(:indexed_group_notification_settings) do
- @group.notification_settings.where(user_id: user.id)
- .where.not(level: NotificationSetting.levels[:global])
- .index_by(&:source_id)
- end
+ @group
+ .notification_settings(hierarchy_order: :asc)
+ .where(user: user)
+ .where.not(level: NotificationSetting.levels[:global])
+ .first
end
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 832c8417b5b..dd0654aec0b 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -48,7 +48,7 @@ class ProjectStatistics < ApplicationRecord
# older migrations fail due to non-existent attribute without this
def packages_size
- has_attribute?(:packages_size) ? super.to_i : 0
+ has_attribute?(:packages_size) ? super : 0
end
def update_storage_size
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
deleted file mode 100644
index fac3c3dcb8f..00000000000
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-##
-# TODO: Remove this uploader when we remove :ci_enable_legacy_artifacts feature flag
-# See https://gitlab.com/gitlab-org/gitlab-ce/issues/58595
-class LegacyArtifactUploader < GitlabUploader
- extend Workhorse::UploadPath
- include ObjectStorage::Concern
-
- ObjectNotReadyError = Class.new(StandardError)
-
- storage_options Gitlab.config.artifacts
-
- alias_method :upload, :model
-
- def store_dir
- dynamic_segment
- end
-
- private
-
- def dynamic_segment
- raise ObjectNotReadyError, 'Build is not ready' unless model.id
-
- File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
- end
-end
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 5e4595d930b..a19c8911559 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -7,28 +7,7 @@
%hr
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
- .row
- .form-group.project-name.col-sm-12
- = label_tag :name, _('Project name'), class: 'label-bold'
- = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
- .form-group.col-12.col-sm-6
- = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
- .form-group
- .input-group
- - if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
- .input-group-text
- = root_url
- = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
-
- - else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
- .input-group-text.border-0
- #{user_url(current_user.username)}/
- = hidden_field_tag :namespace_id, value: current_user.namespace_id
- .form-group.col-12.col-sm-6.project-path
- = label_tag :path, _('Project slug'), class: 'label-bold'
- = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
+ = render 'import/shared/new_project_form'
.row
.form-group.col-md-12
diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml
index 056e4922b9e..df00c4d2179 100644
--- a/app/views/import/manifest/new.html.haml
+++ b/app/views/import/manifest/new.html.haml
@@ -4,9 +4,5 @@
%h3.page-title
= _('Manifest file import')
-- if @errors.present?
- .alert.alert-danger
- - @errors.each do |error|
- = error
-
+= render 'import/shared/errors'
= render 'form'
diff --git a/app/views/import/phabricator/new.html.haml b/app/views/import/phabricator/new.html.haml
new file mode 100644
index 00000000000..811e126579e
--- /dev/null
+++ b/app/views/import/phabricator/new.html.haml
@@ -0,0 +1,25 @@
+- title = _('Phabricator Server Import')
+- page_title title
+- breadcrumb_title title
+- header_title _("Projects"), root_path
+
+%h3.page-title
+ = icon 'issues', text: _('Import tasks from Phabricator into issues')
+
+= render 'import/shared/errors'
+
+= form_tag import_phabricator_path, class: 'new_project', method: :post do
+ = render 'import/shared/new_project_form'
+
+ %h4.prepend-top-0= _('Enter in your Phabricator Server URL and personal access token below')
+
+ .form-group.row
+ = label_tag :phabricator_server_url, _('Phabricator Server URL'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :phabricator_server_url, params[:phabricator_server_url], class: 'form-control append-right-8', placeholder: 'https://your-phabricator-server', size: 40
+ .form-group.row
+ = label_tag :api_token, _('API Token'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = password_field_tag :api_token, params[:api_token], class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
+ .form-actions
+ = submit_tag _('Import tasks'), class: 'btn btn-success'
diff --git a/app/views/import/shared/_errors.html.haml b/app/views/import/shared/_errors.html.haml
new file mode 100644
index 00000000000..de60c15351f
--- /dev/null
+++ b/app/views/import/shared/_errors.html.haml
@@ -0,0 +1,4 @@
+- if @errors.present?
+ .alert.alert-danger
+ - @errors.each do |error|
+ = error
diff --git a/app/views/import/shared/_new_project_form.html.haml b/app/views/import/shared/_new_project_form.html.haml
new file mode 100644
index 00000000000..4d13d4f2869
--- /dev/null
+++ b/app/views/import/shared/_new_project_form.html.haml
@@ -0,0 +1,21 @@
+.row
+ .form-group.project-name.col-sm-12
+ = label_tag :name, _('Project name'), class: 'label-bold'
+ = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
+ .form-group.col-12.col-sm-6
+ = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
+ .form-group
+ .input-group.flex-nowrap
+ - if current_user.can_select_namespace?
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
+ .input-group-text
+ = root_url
+ = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
+ - else
+ .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-text.border-0
+ #{user_url(current_user.username)}/
+ = hidden_field_tag :namespace_id, value: current_user.namespace_id
+ .form-group.col-12.col-sm-6.project-path
+ = label_tag :path, _('Project slug'), class: 'label-bold'
+ = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 1823f191fb3..c90a0b3e329 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -26,7 +26,9 @@
%li
Your Commit Email will be used for web based operations, such as edits and merges.
%li
- Your Notification Email will be used for account notifications.
+ Your Default Notification Email will be used for account notifications if a
+ = link_to 'group-specific email address', profile_notifications_path
+ is not set.
%li
Your Public Email will be displayed on your public profile.
%li
@@ -41,7 +43,7 @@
- if @primary_email === current_user.public_email
%span.badge.badge-info Public email
- if @primary_email === current_user.notification_email
- %span.badge.badge-info Notification email
+ %span.badge.badge-info Default notification email
- @emails.each do |email|
%li
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index a12246bcdcc..cf17ee44145 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -1,12 +1,17 @@
-%li.notification-list-item
- %span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+.gl-responsive-table-row.notification-list-item
+ .table-section.section-40
+ %span.notification.fa.fa-holder.append-right-5
+ - if setting.global?
+ = notification_icon(current_user.global_notification_setting.level)
+ - else
+ = notification_icon(setting.level)
- %span.str-truncated
- = link_to group.name, group_path(group)
+ %span.str-truncated
+ = link_to group.name, group_path(group)
- .float-right
+ .table-section.section-30.text-right
= render 'shared/notifications/button', notification_setting: setting
+
+ .table-section.section-30
+ = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
+ = f.select :notification_email, @user.all_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index fa35fbd3961..1f311e9a4a4 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -41,9 +41,8 @@
%h5
= _('Groups (%{count})') % { count: @group_notifications.count }
%div
- %ul.bordered-list
- - @group_notifications.each do |setting|
- = render 'group_settings', setting: setting, group: setting.source
+ - @group_notifications.each do |setting|
+ = render 'group_settings', setting: setting, group: setting.source
%h5
= _('Projects (%{count})') % { count: @project_notifications.count }
%p.account-well
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 0b4b9841ea1..ac8c31189d0 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Edit Password"
-- page_title "Password"
+- breadcrumb_title _('Edit Password')
+- page_title _('Password')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -7,28 +7,29 @@
%h4.prepend-top-0
= page_title
%p
- After a successful password update, you will be redirected to the login page where you can log in with your new password.
+ = _('After a successful password update, you will be redirected to the login page where you can log in with your new password.')
.col-lg-8
%h5.prepend-top-0
- Change your password
- - unless @user.password_automatically_set?
- or recover your current one
+ - if @user.password_automatically_set
+ = _('Change your password')
+ - else
+ = _('Change your password or recover your current one')
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
= form_errors(@user)
- unless @user.password_automatically_set?
.form-group
- = f.label :current_password, class: 'label-bold'
+ = f.label :current_password, _('Current password'), class: 'label-bold'
= f.password_field :current_password, required: true, class: 'form-control'
%p.form-text.text-muted
- You must provide your current password in order to change it.
+ = _('You must provide your current password in order to change it.')
.form-group
- = f.label :password, 'New password', class: 'label-bold'
+ = f.label :password, _('New password'), class: 'label-bold'
= f.password_field :password, required: true, class: 'form-control'
.form-group
- = f.label :password_confirmation, class: 'label-bold'
+ = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
- = f.submit 'Save password', class: "btn btn-success append-right-10"
+ = f.submit _('Save password'), class: "btn btn-success append-right-10"
- unless @user.password_automatically_set?
- = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
+ = link_to _('I forgot my password'), reset_profile_password_path, method: :put, class: "account-btn-link"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 081166270ab..ce60455ab89 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -14,17 +14,17 @@
- unless @user.password_automatically_set?
.form-group.row
.col-sm-2.col-form-label
- = f.label :current_password
+ = f.label :current_password, _('Current password')
.col-sm-10
= f.password_field :current_password, required: true, class: 'form-control'
.form-group.row
.col-sm-2.col-form-label
- = f.label :password
+ = f.label :password, _('New password')
.col-sm-10
= f.password_field :password, required: true, class: 'form-control'
.form-group.row
.col-sm-2.col-form-label
- = f.label :password_confirmation
+ = f.label :password_confirmation, _('Password confirmation')
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index 759d39cf5f5..be0af977011 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -1,8 +1,6 @@
%p.slead
- Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
- time each to regain access to your account. Please save them in a safe place, or you
- %b will
- lose access to your account.
+ - lose_2fa_message = _('Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account.') % { b_start:'<b>', b_end:'</b>' }
+ = lose_2fa_message.html_safe
.codes.card
%ul
@@ -11,5 +9,5 @@
%span.monospace= code
.d-flex
- = link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10'
- = link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
+ = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/profiles/two_factor_auths/codes.html.haml b/app/views/profiles/two_factor_auths/codes.html.haml
index addf356697a..53907ebffab 100644
--- a/app/views/profiles/two_factor_auths/codes.html.haml
+++ b/app/views/profiles/two_factor_auths/codes.html.haml
@@ -1,5 +1,6 @@
-- page_title 'Recovery Codes', 'Two-factor Authentication'
+- page_title _('Recovery Codes'), _('Two-factor Authentication')
-%h3.page-title Two-factor Authentication Recovery codes
+%h3.page-title
+ = _('Two-factor Authentication Recovery codes')
%hr
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml
index e330aadac13..973eb8136c4 100644
--- a/app/views/profiles/two_factor_auths/create.html.haml
+++ b/app/views/profiles/two_factor_auths/create.html.haml
@@ -1,6 +1,6 @@
-- page_title 'Two-factor Authentication', 'Account'
+- page_title _('Two-factor Authentication'), _('Account')
.alert.alert-success
- Congratulations! You have enabled Two-factor Authentication!
+ = _('Congratulations! You have enabled Two-factor Authentication!')
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index d986c566928..5501e63e027 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,72 +1,68 @@
-- page_title 'Two-Factor Authentication', 'Account'
-- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path)
+- page_title _('Two-Factor Authentication'), _('Account')
+- add_to_breadcrumbs(_('Two-Factor Authentication'), profile_account_path)
- @content_class = "limit-container-width" unless fluid_layout
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Two-Factor Authenticator
+ = _('Register Two-Factor Authenticator')
%p
- Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
+ = _('Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
.col-lg-8
- if current_user.two_factor_otp_enabled?
%p
- You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
+ = _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.")
%p
- If you lose your recovery codes you can generate new ones, invalidating all previous codes.
+ = _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.')
%div
- = link_to 'Disable two-factor authentication', profile_two_factor_auth_path,
+ = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path,
method: :delete,
- data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
+ data: { confirm: _('Are you sure? This will invalidate your registered applications and U2F devices.') },
class: 'btn btn-danger append-right-10'
= form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f|
- = submit_tag 'Regenerate recovery codes', class: 'btn'
+ = submit_tag _('Regenerate recovery codes'), class: 'btn'
- else
%p
- Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a>
- or Google Authenticator from your application repository and scan this QR code.
- More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
+ - help_link_start = '<a href="%{url}" target="_blank">' % { url: help_page_path('user/profile/account/two_factor_authentication') }
+ - register_2fa_token = _('Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}.') % { free_otp_link:'<a href="https://freeotp.github.io/">FreeOTP</a>', help_link_start:help_link_start, help_link_end:'</a>' }
+ = register_2fa_token.html_safe
.row.append-bottom-10
.col-md-4
= raw @qr_code
.col-md-8
.account-well
%p.prepend-top-0.append-bottom-0
- Can't scan the code?
+ = _("Can't scan the code?")
%p.prepend-top-0.append-bottom-0
- To add the entry manually, provide the following details to the application on your phone.
+ = _('To add the entry manually, provide the following details to the application on your phone.')
%p.prepend-top-0.append-bottom-0
- Account:
- = @account_string
+ = _('Account: %{account}') % { account: @account_string }
%p.prepend-top-0.append-bottom-0
- Key:
- = current_user.otp_secret.scan(/.{4}/).join(' ')
+ = _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') }
%p.two-factor-new-manual-content
- Time based: Yes
+ = _('Time based: Yes')
= form_tag profile_two_factor_auth_path, method: :post do |f|
- if @error
.alert.alert-danger
= @error
.form-group
- = label_tag :pin_code, nil, class: "label-bold"
+ = label_tag :pin_code, _('Pin code'), class: "label-bold"
= text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default
- = submit_tag 'Register with two-factor app', class: 'btn btn-success'
+ = submit_tag _('Register with two-factor app'), class: 'btn btn-success'
%hr
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Universal Two-Factor (U2F) Device
+ = _('Register Universal Two-Factor (U2F) Device')
%p
- Use a hardware device to add the second factor of authentication.
+ = _('Use a hardware device to add the second factor of authentication.')
%p
- As U2F devices are only supported by a few browsers, we require that you set up a
- two-factor authentication app before a U2F device. That way you'll always be able to
- log in - even when you're using an unsupported browser.
+ = _("As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser.")
.col-lg-8
- if @u2f_registration.errors.present?
= form_errors(@u2f_registration)
@@ -74,7 +70,8 @@
%hr
- %h5 U2F Devices (#{@u2f_registrations.length})
+ %h5
+ = _('U2F Devices (%{length})') % { length: @u2f_registrations.length }
- if @u2f_registrations.present?
.table-responsive
@@ -85,16 +82,16 @@
%col{ width: "20%" }
%thead
%tr
- %th Name
- %th Registered On
+ %th= _('Name')
+ %th= s_('2FADevice|Registered On')
%th
%tbody
- @u2f_registrations.each do |registration|
%tr
- %td= registration.name.presence || "<no name set>"
+ %td= registration.name.presence || _("<no name set>")
%td= registration.created_at.to_date.to_s(:medium)
- %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." }
+ %td= link_to _('Delete'), profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') }
- else
.settings-message.text-center
- You don't have any U2F devices registered yet.
+ = _("You don't have any U2F devices registered yet.")
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 9c854369c93..b5678b56ca6 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -63,6 +63,13 @@
= link_to new_import_manifest_path, class: 'btn import_manifest', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "manifest_file" } do
= icon('file-text-o', text: 'Manifest file')
+ - if phabricator_import_enabled?
+ %div
+ = link_to new_import_phabricator_path, class: 'btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do
+ = custom_icon('issues')
+ = _("Phabricator Tasks")
+
+
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
= form_for @project, html: { class: 'new_project' } do |f|
%hr
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
index fd576e4fbea..e8f3d888cce 100644
--- a/app/views/shared/_old_visibility_level.html.haml
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -1,6 +1,6 @@
.form-group.row
.col-sm-2.col-form-label
= _('Visibility level')
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ = link_to icon('question-circle'), help_page_path("public_access/public_access"), target: '_blank'
.col-sm-10
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 31cc0c091dd..749aa258af6 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,14 +1,14 @@
- btn_class = local_assigns.fetch(:btn_class, nil)
- if notification_setting
- .js-notification-dropdown.notification-dropdown.home-panel-action-button.dropdown.inline
+ .js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
= f.hidden_field :level, class: "notification_setting_level"
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
@@ -16,9 +16,11 @@
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
- = icon("bell", class: "js-notification-loading")
- = notification_title(notification_setting.level)
- = icon("caret-down")
+ .float-left
+ = icon("bell", class: "js-notification-loading")
+ = notification_title(notification_setting.level)
+ .float-right
+ = icon("caret-down")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
diff --git a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
new file mode 100644
index 00000000000..45770e1012c
--- /dev/null
+++ b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
@@ -0,0 +1,5 @@
+---
+title: Add API support for committing changes to different projects in same fork network
+merge_request: 27915
+author:
+type: added
diff --git a/changelogs/unreleased/59587-add-graphql-logging.yml b/changelogs/unreleased/59587-add-graphql-logging.yml
new file mode 100644
index 00000000000..74c2a734f37
--- /dev/null
+++ b/changelogs/unreleased/59587-add-graphql-logging.yml
@@ -0,0 +1,5 @@
+---
+title: Add dedicated logging for GraphQL queries
+merge_request: 27885
+author:
+type: other
diff --git a/changelogs/unreleased/60987-emoji-picker-popup.yml b/changelogs/unreleased/60987-emoji-picker-popup.yml
new file mode 100644
index 00000000000..3bccec8e164
--- /dev/null
+++ b/changelogs/unreleased/60987-emoji-picker-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Fix emoji picker visibility issue
+merge_request: 28984
+author:
+type: fixed
diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
new file mode 100644
index 00000000000..46d3f439a44
--- /dev/null
+++ b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
@@ -0,0 +1,5 @@
+---
+title: Change collapse icon size to size of profile picture
+merge_request: 28512
+author: Marcel van Remmerden
+type: other
diff --git a/changelogs/unreleased/62432-fix-participants-wrapping.yml b/changelogs/unreleased/62432-fix-participants-wrapping.yml
new file mode 100644
index 00000000000..a7e4bd372de
--- /dev/null
+++ b/changelogs/unreleased/62432-fix-participants-wrapping.yml
@@ -0,0 +1,5 @@
+---
+title: Fix participants list wrapping
+merge_request: 28873
+author:
+type: fixed
diff --git a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
new file mode 100644
index 00000000000..bd005206d4e
--- /dev/null
+++ b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
@@ -0,0 +1,5 @@
+---
+title: Forbid NULL in project_statistics.packages_size
+merge_request: 28400
+author:
+type: other
diff --git a/changelogs/unreleased/add-constraint-for-milestone-dates.yml b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
new file mode 100644
index 00000000000..485149cf62e
--- /dev/null
+++ b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
@@ -0,0 +1,5 @@
+---
+title: Limit milestone dates to before year 9999
+merge_request: 28742
+author: Luke Picciau
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.43.0.yml b/changelogs/unreleased/gitaly-version-v1.43.0.yml
new file mode 100644
index 00000000000..67acd2725e1
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.43.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.43.0
+merge_request: 28867
+author:
+type: changed
diff --git a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
new file mode 100644
index 00000000000..35515c9d639
--- /dev/null
+++ b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Open visibility help link in a new tab
+merge_request: 28603
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
new file mode 100644
index 00000000000..acde65af2d4
--- /dev/null
+++ b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove legacy artifact related code
+merge_request: 26475
+author:
+type: other
diff --git a/changelogs/unreleased/weimeng-email-routing.yml b/changelogs/unreleased/weimeng-email-routing.yml
new file mode 100644
index 00000000000..6536433bd03
--- /dev/null
+++ b/changelogs/unreleased/weimeng-email-routing.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to define notification email addresses for groups you belong to.
+merge_request: 25299
+author:
+type: added
diff --git a/config/routes/import.rb b/config/routes/import.rb
index 24013eb2c88..9fe2688de1e 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -67,4 +67,6 @@ namespace :import do
get :jobs
post :upload
end
+
+ resource :phabricator, only: [:create, :new], controller: :phabricator
end
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index c1cac3905f1..0e213b0b989 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -17,7 +17,11 @@ resource :profile, only: [:show, :update] do
delete :unlink
end
end
- resource :notifications, only: [:show, :update]
+
+ resource :notifications, only: [:show, :update] do
+ resources :groups, only: :update
+ end
+
resource :password, only: [:new, :create, :edit, :update] do
member do
put :reset
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 1e94bdc245f..840dc4c7844 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -124,6 +124,44 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
put :revoke
end
end
+
+ resources :milestones, constraints: { id: /\d+/ } do
+ member do
+ post :promote
+ put :sort_issues
+ put :sort_merge_requests
+ get :merge_requests
+ get :participants
+ get :labels
+ end
+ end
+
+ resources :labels, except: [:show], constraints: { id: /\d+/ } do
+ collection do
+ post :generate
+ post :set_priorities
+ end
+
+ member do
+ post :promote
+ post :toggle_subscription
+ delete :remove_priority
+ end
+ end
+
+ resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
+ member do
+ put :test
+ end
+ end
+
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
+ resources :releases, only: [:index]
+ resources :forks, only: [:index, :new, :create]
+ resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
+
+ resource :import, only: [:new, :create, :show]
+ resource :avatar, only: [:show, :destroy]
end
# End of the /-/ scope.
@@ -132,7 +170,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
#
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} }
- resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
@@ -159,12 +196,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
- member do
- put :test
- end
- end
-
resource :mattermost, only: [:new, :create]
namespace :prometheus do
@@ -173,10 +204,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :releases, only: [:index]
- resources :forks, only: [:index, :new, :create]
- resource :import, only: [:new, :create, :show]
-
resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do
member do
get :commit_change_content
@@ -372,31 +399,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :milestones, constraints: { id: /\d+/ } do
- member do
- post :promote
- put :sort_issues
- put :sort_merge_requests
- get :merge_requests
- get :participants
- get :labels
- end
- end
-
- resources :labels, except: [:show], constraints: { id: /\d+/ } do
- collection do
- post :generate
- post :set_priorities
- end
-
- member do
- post :promote
- post :toggle_subscription
- delete :remove_priority
- end
- end
-
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
+
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
@@ -408,14 +412,13 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :create_merge_request
get :discussions, format: :json
end
+
collection do
post :bulk_update
post :import_csv
end
end
- resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
-
resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
delete :delete_attachment
@@ -426,8 +429,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
-
resources :todos, only: [:create]
resources :uploads, only: [:create] do
@@ -510,7 +511,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
as: :project) do
Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
:network, :graphs, :autocomplete_sources,
- :project_members, :deploy_keys, :deploy_tokens)
+ :project_members, :deploy_keys, :deploy_tokens,
+ :labels, :milestones, :services, :boards, :releases,
+ :forks, :group_links, :import, :avatar)
end
end
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 8bc2426ec4c..0615da2d241 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -91,3 +91,4 @@
- [chat_notification, 2]
- [migrate_external_diffs, 1]
- [update_project_statistics, 1]
+ - [phabricator_import_import_tasks, 1]
diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
new file mode 100644
index 00000000000..2f3069032a1
--- /dev/null
+++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :notification_settings, :notification_email, :string
+ end
+end
diff --git a/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
new file mode 100644
index 00000000000..eba154df496
--- /dev/null
+++ b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ChangePackagesSizeDefaultsInProjectStatistics < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ change_column_default :project_statistics, :packages_size, 0
+
+ update_column_in_batches(:project_statistics, :packages_size, 0) do |table, query|
+ query.where(table[:packages_size].eq(nil))
+ end
+
+ change_column_null :project_statistics, :packages_size, false
+ end
+
+ def down
+ change_column_null :project_statistics, :packages_size, true
+ change_column_default :project_statistics, :packages_size, nil
+ end
+end
diff --git a/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
new file mode 100644
index 00000000000..86fe09d7573
--- /dev/null
+++ b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class LimitMilestoneDateYearsTo4Digits < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index", "remove_concurrent_index" or
+ # "add_column_with_default" you must disable the use of transactions
+ # as these methods can not run in an existing transaction.
+ # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+ # that either of them is the _only_ method called in the migration,
+ # any other changes should go in a separate migration.
+ # This ensures that upon failure _only_ the index creation or removing fails
+ # and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ Milestone.where("start_date > '9999-12-31'").update_all(
+ "start_date = '9999-12-31'"
+ )
+ Milestone.where("due_date > '9999-12-31'").update_all(
+ "due_date = '9999-12-31'"
+ )
+ end
+end
diff --git a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
index 21d7049b998..ae93a76575a 100644
--- a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
+++ b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
@@ -9,23 +9,8 @@ class GenerateLetsEncryptPrivateKey < ActiveRecord::Migration[5.1]
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
- class ApplicationSetting < ActiveRecord::Base
- self.table_name = 'application_settings'
-
- attr_encrypted :lets_encrypt_private_key,
- mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
- algorithm: 'aes-256-gcm',
- encode: true
- end
-
+ # we now generate this key on the fly, but since this migration was merged to master, we don't remove it
def up
- ApplicationSetting.reset_column_information
-
- private_key = OpenSSL::PKey::RSA.new(4096).to_pem
- ApplicationSetting.find_each do |setting|
- setting.update!(lets_encrypt_private_key: private_key)
- end
end
def down
diff --git a/db/schema.rb b/db/schema.rb
index ef272be22ee..378cb9c65d0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1516,6 +1516,7 @@ ActiveRecord::Schema.define(version: 20190527194900) do
t.boolean "success_pipeline"
t.boolean "push_to_merge_request"
t.boolean "issue_due"
+ t.string "notification_email"
t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree
t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree
@@ -1743,7 +1744,7 @@ ActiveRecord::Schema.define(version: 20190527194900) do
t.bigint "repository_size", default: 0, null: false
t.bigint "lfs_objects_size", default: 0, null: false
t.bigint "build_artifacts_size", default: 0, null: false
- t.bigint "packages_size"
+ t.bigint "packages_size", default: 0, null: false
t.bigint "wiki_size"
t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree
t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index a7e57e44e86..ac41f9177dd 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -288,6 +288,20 @@ installations from source.
It logs information whenever [Rack Attack] registers an abusive request.
+## `graphql_json.log`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59587) in GitLab 12.0.
+
+This file lives in `/var/log/gitlab/gitlab-rails/graphql_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/graphql_json.log` for
+installations from source.
+
+GraphQL queries are recorded in that file. For example:
+
+```json
+{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7}
+```
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index b295b7d5dc4..0b4c1ae15d6 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -205,25 +205,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
-## Create or repair repository hooks symlink
-
-If the GitLab shell hooks directory location changes or another circumstance
-leads to the hooks symlink becoming missing or invalid, run this Rake task
-to create or repair the symlinks.
-
-**Omnibus Installation**
-
-```
-sudo gitlab-rake gitlab:shell:create_hooks
-```
-
-**Source Installation**
-
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
-```
-
## Check TCP connectivity to a remote site
Sometimes you need to know if your GitLab installation can connect to a TCP
diff --git a/doc/analytics/README.md b/doc/analytics/README.md
index 6b63edb5174..bfb15f6c4f3 100644
--- a/doc/analytics/README.md
+++ b/doc/analytics/README.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter'
+redirect_to: '../user/group/index.md#user-contribution-analysis-starter'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter)
+This document was moved to [another location](../user/group/index.md#user-contribution-analysis-starter)
diff --git a/doc/analytics/contribution_analytics.md b/doc/analytics/contribution_analytics.md
index 38d71263bc1..e36f55071a4 100644
--- a/doc/analytics/contribution_analytics.md
+++ b/doc/analytics/contribution_analytics.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html'
+redirect_to: '../user/group/contribution_analytics/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html).
+This document was moved to [another location](../user/group/contribution_analytics/index.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 92f53c7b5e6..25015fad9e3 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -75,6 +75,7 @@ POST /projects/:id/repository/commits
| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. |
| `commit_message` | string | yes | Commit message |
| `start_branch` | string | no | Name of the branch to start the new commit from |
+| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
index 4ce96fcb230..3e6f3130437 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html'
+redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html).
+This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md).
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
index 01c31728c21..adaa120a37e 100644
--- a/doc/customization/issue_and_merge_request_template.md
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter'
+redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter'
---
-This document was moved to [description_templates](https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter).
+This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter).
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 857595330aa..d0db1a61935 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -459,15 +459,6 @@ Resolving an EE template path that is relative to the CE view path will not work
= render_if_exists 'projects/button' # Will render `ee/app/views/projects/_button`
```
-You should not explicitly set render options like `partial` or provide a `locals` hash.
-The first argument should be a path string and the second can be a hash replacing `locals`.
-
-```ruby
-render partial: 'projects/button', locals: { project: project }
-# becomes
-render_if_exists 'projects/button', project: project
-```
-
#### Using `render_ce`
For `render` and `render_if_exists`, they search for the EE partial first,
diff --git a/qa/docs/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md
index 62a90563bf0..89500ef9a90 100644
--- a/qa/docs/best_practices.md
+++ b/doc/development/testing_guide/end_to_end/best_practices.md
@@ -35,4 +35,4 @@ Finally, interacting with the application only by its GUI generates a higher rat
- Building state through the GUI is time consuming and it's not sustainable as the test suite grows.
- When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness.
-Now that we are aware of all of it, [let's go create some tests](writing_tests_from_scratch.md#this-document-covers-the-following-items).
+Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md).
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end/index.md
index fc7aaedca29..afd81ff00b2 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -65,7 +65,7 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
-![QA on merge requests CI/CD architecture](img/qa_on_merge_requests_cicd_architecture.png)
+![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png)
<details>
<summary>Show mermaid source</summary>
@@ -136,6 +136,12 @@ Once you decided where to put [test environment orchestration scenarios] and
the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
instance-level scenarios][instance-level scenarios].
+Continued reading:
+
+- [Quick Start Guide](quick_start_guide.md)
+- [Style Guide](style_guide.md)
+- [Best Practices](best_practices.md)
+
## Where can I ask for help?
You can ask question in the `#quality` channel on Slack (GitLab internal) or
@@ -149,7 +155,7 @@ you can find an issue you would like to work on in
[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
-[review-apps]: ./review_apps.md
+[review-apps]: ../review_apps.md
[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
diff --git a/qa/qa/page/README.md b/doc/development/testing_guide/end_to_end/page_objects.md
index d0de33892c4..d0de33892c4 100644
--- a/qa/qa/page/README.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
diff --git a/qa/docs/writing_tests_from_scratch.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
index 06b60a1ef45..afe76acf9c9 100644
--- a/qa/docs/writing_tests_from_scratch.md
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -17,10 +17,10 @@ If you don't exactly understand what we mean by **not everything needs to happen
- [2.](#2-test-skeleton) Creating the skeleton of the test file (`*_spec.rb`)
- [3.](#3-test-cases-mvc) The [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of the test cases' logic
- [4.](#4-extracting-duplicated-code) Extracting duplicated code into methods
-- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects](./qa/qa/page/README.md)
+- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects]
- [6.](#6-optimization) Optimizing the test suite
- [7.](#7-resources) Using and implementing resources
-- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects](./qa/qa/page/README.md)
+- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects]
### 0. Are end-to-end tests needed?
@@ -111,7 +111,7 @@ end
> Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later.
-> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects](./qa/qa/page/README.md) before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language.
+> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects] before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language.
Below are the steps that the test covers:
@@ -275,7 +275,7 @@ In the `before` block we create all the application state needed for the tests t
> A project is created in the background by creating the `issue` resource.
-> When [creating the resources](./qa/qa/resource/README.md), notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource.
+> When creating the [Resources], notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource.
> What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage.
@@ -283,7 +283,7 @@ In the `before` block we create all the application state needed for the tests t
### 6. Optimization
-As already mentioned in the [best practices](./BEST_PRACTICES.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible.
+As already mentioned in the [best practices](best_practices.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible.
> Note that end-to-end tests are slow to run and so they can have several actions and assertions in a single test, which helps us get feedback from the tests sooner. In comparison, unit tests are much faster to run and can exercise every little piece of the application in isolation, and so they usually have only one assertion per test.
@@ -339,7 +339,7 @@ To address point 1, we changed the test implementation from two `it` blocks into
**Note:** When writing this document, some code that is now merged to master was not implemented yet, but we left them here for the readers to understand the whole process of end-to-end test creation.
-You can think of [resources](qa/qa/resource/README.md) as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI.
+You can think of [Resources] as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI.
With that in mind, resources can be a project, an epic, an issue, a label, a commit, etc.
@@ -439,7 +439,7 @@ Page Objects are used in end-to-end tests for maintenance reasons, where a page'
> Page Objects are auto-loaded in the `qa/qa.rb` file and available in all the test files (`*_spec.rb`).
-Take a look at [this document for a detailed explanation about Page Objects](./qa/page/README.md).
+Take a look at the [Page Objects] documentation.
Now, let's go back to our example.
@@ -580,3 +580,6 @@ As you might remember, in the Issue Page Object we call this method like this: `
___
With that, you should be able to start writing end-to-end tests yourself. *Congratulations!*
+
+[Page Objects]: page_objects.md
+[Resources]: resources.md
diff --git a/qa/qa/resource/README.md b/doc/development/testing_guide/end_to_end/resources.md
index 2c8859b6599..1e32db4f633 100644
--- a/qa/qa/resource/README.md
+++ b/doc/development/testing_guide/end_to_end/resources.md
@@ -5,11 +5,11 @@ be created via the API or the CLI.
## How to properly implement a resource class?
-All resource classes should inherit from [`Resource::Base`](./base.rb).
+All resource classes should inherit from `Resource::Base`.
There is only one mandatory method to implement to define a resource class.
This is the `#fabricate!` method, which is used to build the resource via the
-browser UI. Note that you should only use [Page objects](../page/README.md) to
+browser UI. Note that you should only use [Page objects](page_objects.md) to
interact with a Web page in this method.
Here is an imaginary example:
@@ -74,7 +74,7 @@ module QA
end
```
-The [`Project` resource](./project.rb) is a good real example of Browser
+The `Project` resource is a good real example of Browser
UI and API implementations.
#### Resource attributes
diff --git a/qa/docs/guidelines.md b/doc/development/testing_guide/end_to_end/style_guide.md
index cd4b939fd71..0272e1810f2 100644
--- a/qa/docs/guidelines.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -1,6 +1,6 @@
-# Style guide for writing E2E tests
+# Style guide for writing end-to-end tests
-This document describes the conventions used at GitLab for writing E2E tests using the GitLab QA project.
+This document describes the conventions used at GitLab for writing End-to-end (E2E) tests using the GitLab QA project.
## `click_` versus `go_to_`
@@ -45,7 +45,7 @@ Notice that in the above example, before clicking the `:operations_environments_
> We can create these methods as helpers to abstract multi-step navigation.
-### Element Naming Convention
+### Element naming convention
When adding new elements to a page, it's important that we have a uniform element naming convention.
@@ -69,7 +69,8 @@ We follow a simple formula roughly based on hungarian notation.
#### Examples
-**Good**
+**Good**
+
```ruby
view '...' do
element :edit_button
@@ -80,7 +81,8 @@ view '...' do
end
```
-**Bad**
+**Bad**
+
```ruby
view '...' do
# `_confirmation` should be `_field`. what sort of confirmation? a checkbox confirmation? no real way to disambiguate.
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index ecad9ba48a3..93ee2a6371a 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -71,7 +71,7 @@ Everything you should know about how to test Rake tasks.
---
-## [End-to-end tests](end_to_end_tests.md)
+## [End-to-end tests](end_to_end/index.md)
Everything you should know about how to run end-to-end tests using
[GitLab QA][gitlab-qa] testing framework.
diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md
index 30d861d7d68..c9d3238fbe9 100644
--- a/doc/development/testing_guide/smoke.md
+++ b/doc/development/testing_guide/smoke.md
@@ -17,7 +17,7 @@ Currently, our suite consists of this basic functionality coverage:
Smoke tests have the `:smoke` RSpec metadata.
-See [End-to-end Testing](./end_to_end_tests.md) for more details about
+See [End-to-end Testing](end_to_end/index.md) for more details about
end-to-end tests.
---
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index b5155b6b7fa..e1ce4d3b7d1 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -178,7 +178,7 @@ Every new feature should come with a [test plan].
| ---------- | -------------- | ----- |
| `qa/qa/specs/features/` | [Capybara] + [RSpec] + Custom QA framework | Tests should be placed under their corresponding [Product category] |
-> See [end-to-end tests](end_to_end_tests.md) for more information.
+> See [end-to-end tests](end_to_end/index.md) for more information.
Note that `qa/spec` contains unit tests of the QA framework itself, not to be
confused with the application's [unit tests](#unit-tests) or
diff --git a/doc/git_hooks/git_hooks.md b/doc/git_hooks/git_hooks.md
index 9b8ad1578a0..b251e58410a 100644
--- a/doc/git_hooks/git_hooks.md
+++ b/doc/git_hooks/git_hooks.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/push_rules/push_rules.html'
+redirect_to: '../push_rules/push_rules.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/push_rules/push_rules.html)
+This document was moved to [another location](../push_rules/push_rules.md)
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 785e2ffb650..a9ae4fb23f9 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -16,7 +16,7 @@ To create a project in GitLab:
- [Import a project](../user/project/import/index.md) from a different repository,
if enabled on your GitLab instance. Contact your GitLab admin if this
is unavailable.
- - Run [CI/CD pipelines for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]**
+ - Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
## Blank projects
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 0000e03f1d7..c9fed36f258 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -383,7 +383,7 @@ after the instance is created.
CAUTION: **Caution:**
We **do not** recommend using the AWS Elastic File System (EFS), as it can result
-in [significantly degraded performance](../../administration/high_availability/nfs.html#avoid-using-awss-elastic-file-system-efs).
+in [significantly degraded performance](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
### Configure security group
@@ -649,12 +649,12 @@ Have a read through these other resources and feel free to
[open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new)
to request additional material:
-- [GitLab High Availability](https://docs.gitlab.com/ee/administration/high_availability/):
+- [GitLab High Availability](../../administration/high_availability/README.md):
GitLab supports several different types of clustering and high-availability.
-- [Geo replication](https://docs.gitlab.com/ee/administration/geo/replication/):
+- [Geo replication](../../administration/geo/replication/index.md):
Geo is the solution for widely distributed development teams.
- [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know
about administering your GitLab instance.
-- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html):
+- [Upload a license](../../user/admin_area/license.md):
Activate all GitLab Enterprise Edition functionality with a license.
- [Pricing](https://about.gitlab.com/pricing): Pricing for the different tiers.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 7312bf2d4f7..fd0f7b0d328 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -10,7 +10,7 @@ If you're not sure if Kubernetes is for you, our
[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
are mature, scalable, support [high availability](../../administration/high_availability/README.md)
and are used today on GitLab.com.
-It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
+It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](../../user/project/clusters/index.md).
The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md)
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index f6a52205a0e..4931a69f2a3 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -105,9 +105,9 @@ features of GitLab work with MySQL/MariaDB:
1. MySQL support for subgroups was [dropped with GitLab 9.3][post].
See [issue #30472][30472] for more information.
-1. Geo does [not support MySQL](https://docs.gitlab.com/ee/administration/geo/replication/database.html). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
+1. Geo does [not support MySQL](../administration/geo/replication/database.md). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
1. [Zero downtime migrations](../update/README.md#upgrading-without-downtime) do not work with MySQL.
-1. [Database load balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html) is
+1. [Database load balancing](../administration/database_load_balancing.md) is
supported only for PostgreSQL. **[PREMIUM ONLY]**
1. GitLab [optimizes the loading of dashboard events](https://gitlab.com/gitlab-org/gitlab-ce/issues/31806) using [PostgreSQL LATERAL JOINs](https://blog.heapanalytics.com/postgresqls-powerful-new-join-type-lateral/).
1. In general, SQL optimized for PostgreSQL may run much slower in MySQL due to
@@ -143,14 +143,14 @@ On some systems you may need to install an additional package (e.g.
#### Additional requirements for GitLab Geo
-If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html):
+If you are using [GitLab Geo](../development/geo.md):
- We strongly recommend running Omnibus-managed instances as they are actively
developed and tested. We aim to be compatible with most external (not managed
by Omnibus) databases (for example, AWS RDS) but we do not guarantee
compatibility.
- The
- [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database)
+ [tracking database](../development/geo.md#using-the-tracking-database)
requires the
[postgres_fdw](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
extension.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 15176ede733..0a037b3876b 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -8,8 +8,8 @@ This document describes how to set up Elasticsearch with GitLab. Once enabled,
you'll have the benefit of fast search response times and the advantage of two
special searches:
-- [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html)
-- [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html)
+- [Advanced Global Search](../user/search/advanced_global_search.md)
+- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
## Version Requirements
<!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 15d9d8c9c74..22e07594d6f 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -1,6 +1,6 @@
# SAML OmniAuth Provider
-> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html).
+> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](../user/group/saml_sso/index.md).
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index cd755089be8..71ea2e25533 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -20,8 +20,8 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` |
-Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
-your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
+Note that if you are using the [GitLab Slack application](../user/project/integrations/gitlab_slack_application.md) for
+your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](../user/project/integrations/gitlab_slack_application.md#usage).
## Issue commands
diff --git a/doc/license/README.md b/doc/license/README.md
index b9281fd5299..fd110a39b61 100644
--- a/doc/license/README.md
+++ b/doc/license/README.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/admin_area/license.html'
+redirect_to: '../user/admin_area/license.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/admin_area/license.html).
+This document was moved to [another location](../user/admin_area/license.md).
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
index 7654023f266..e44eab2556e 100644
--- a/doc/push_rules/push_rules.md
+++ b/doc/push_rules/push_rules.md
@@ -61,7 +61,7 @@ The following options are available.
| --------- | :------------: | ----------- |
| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. |
| Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. |
-| Check whether committer is the current authenticated user | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
+| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
| Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. |
| Prevent committing secrets to Git | **Starter** 8.12 | GitLab will reject any files that are likely to contain secrets. Read [what files are forbidden](#prevent-pushing-secrets-to-the-repository). |
| Restrict by commit message | **Starter** 7.10 | Only commit messages that match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. |
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index b6d16536fee..dfd80f8882e 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -38,7 +38,7 @@ Future purchases will use the information in this section. The email listed in t
### Self-managed: Apply your license file
-After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](https://docs.gitlab.com/ee/user/admin_area/license.html#uploading-your-license).
+After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license).
### Link your GitLab.com account with your Customers Portal account
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c2dff21b028..f2df4277ca8 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -108,7 +108,7 @@ sudo -u git -H make
### 8. Install/Update `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
-If you're interested in using GitLab's new [elasticsearch repository indexer](https://docs.gitlab.com/ee/integration/elasticsearch.html#elasticsearch-repository-indexer-beta) (currently in beta)
+If you're interested in using GitLab's new [elasticsearch repository indexer](../integration/elasticsearch.md#elasticsearch-repository-indexer-beta) (currently in beta)
please follow the instructions on the document linked above and enable the
indexer usage in the GitLab admin settings.
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index e74a5c00f7e..428377adb19 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -75,7 +75,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
### 4. Install `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
If you're interested in using GitLab's new [elasticsearch repository
-indexer][indexer-beta] (currently in beta) please follow the instructions on the
+indexer](../integration/elasticsearch.md) (currently in beta) please follow the instructions on the
document linked above and enable the indexer usage in the GitLab admin settings.
### 5. Start application
@@ -133,4 +133,3 @@ Additional instructions here.
[support@gitlab.com]: mailto:support@gitlab.com
[old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update
-[indexer-beta]: https://docs.gitlab.com/ee/integration/elasticsearch.html
diff --git a/doc/user/admin_area/geo_nodes.md b/doc/user/admin_area/geo_nodes.md
index fb0f9a3285d..d99b87cbc5c 100644
--- a/doc/user/admin_area/geo_nodes.md
+++ b/doc/user/admin_area/geo_nodes.md
@@ -5,7 +5,7 @@ type: howto
# Geo nodes admin area **[PREMIUM ONLY]**
You can configure various settings for GitLab Geo nodes. For more information, see
-[Geo documentation](https://docs.gitlab.com/ee/administration/geo/replication/index.md).
+[Geo documentation](../../administration/geo/replication/index.md).
On the primary node, go to **Admin area > Geo**. On secondary nodes, go to **Admin area > Geo > Nodes**.
@@ -29,7 +29,7 @@ changes on the **primary** node!
| Setting | Description |
|---------------------------|-------------|
-| Selective synchronization | Enable Geo [selective sync](https://docs.gitlab.com/ee/administration/geo/replication/configuration.html#selective-synchronization) for this **secondary** node. |
+| Selective synchronization | Enable Geo [selective sync](../../administration/geo/replication/configuration.md#selective-synchronization) for this **secondary** node. |
| Repository sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling repositories. |
| File sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling files. |
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index db5da26ca0a..0fc6ed349ba 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -27,7 +27,7 @@ The Admin Area is made up of the following sections:
| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
| License **[STARTER ONLY]** | Upload, display, and remove [licenses](license.md). |
-| Push Rules **[STARTER]** | Configure pre-defined git [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) for projects. |
+| Push Rules **[STARTER]** | Configure pre-defined git [push rules](../../push_rules/push_rules.md) for projects. |
| Geo **[PREMIUM ONLY]** | Configure and maintain [Geo nodes](geo_nodes.md). |
| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 912c2cff481..9555a695b13 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -40,7 +40,7 @@ In order to change this option:
1. Hit **Save** for the changes to take effect.
NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get
-recognized by GitLab. This can directly conflict with certain [Push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) such as
+recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as
`Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`.
<!-- ## Troubleshooting
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 5c635b09503..adb6868516e 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -119,7 +119,7 @@ container_scanning:
variables:
DOCKER_DRIVER: overlay2
## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
CI_APPLICATION_TAG: $CI_COMMIT_SHA
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
@@ -162,7 +162,7 @@ container_scanning:
variables:
DOCKER_DRIVER: overlay2
## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
CI_APPLICATION_TAG: $CI_COMMIT_SHA
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 8458b4f5de3..9c7b83252b0 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -87,7 +87,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with
[environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-environment-variables-premium)
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium)
work.
While evaluating which environment matches the environment scope of a
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 7493e65e237..853b00f1f67 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -296,7 +296,7 @@ To enable this feature, navigate to the group settings page. Select
Member lock lets a group owner prevent any new project membership to all of the
projects within a group, allowing tighter control over project membership.
-For example, if you want to lock the group for an [Audit Event](https://docs.gitlab.com/ee/administration/audit_events.html),
+For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
enable Member lock to guarantee that project membership cannot be modified during that audit.
To enable this feature:
@@ -315,7 +315,7 @@ request to add a new user to a project through API will not be possible.
Group file templates allow you to share a set of templates for common file
types with every project in a group. It is analogous to the
-[instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html)
+[instance template repository](../admin_area/settings/instance_template_repository.md)
feature, and the selected project should follow the same naming conventions as
are documented on that page.
@@ -346,7 +346,7 @@ Define project templates at a group level by setting a group as the template sou
access each project's settings, and remove any project, all from the same screen.
- **Webhooks**: Configure [webhooks](../project/integrations/webhooks.md) for your group.
- **Kubernetes cluster integration**: Connect your GitLab group with [Kubernetes clusters](clusters/index.md).
-- **Audit Events**: View [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html)
+- **Audit Events**: View [Audit Events](../../administration/audit_events.md)
for the group. **[STARTER ONLY]**
- **Pipelines quota**: Keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md
index 20f76c54ae7..427b474ca39 100644
--- a/doc/user/group/insights/index.md
+++ b/doc/user/group/insights/index.md
@@ -31,7 +31,7 @@ If no configuration was set, a [default configuration file](
https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/fixtures/insights/ee/fixtures/insights/default.yml)
will be used.
-See the [Project's Insights documentation](https://docs.gitlab.com/ee/user/project/insights/index.html) for
+See the [Project's Insights documentation](../../project/insights/index.md) for
more details about the `.gitlab/insights.yml` configuration file.
## Permissions
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index ec27ec3e336..c00628bf909 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -2,7 +2,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10.
-GitLab's [SCIM API](https://docs.gitlab.com/ee/api/scim.html) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
+GitLab's [SCIM API](../../../api/scim.md) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
Currently, the following actions are available:
diff --git a/doc/user/group/security_dashboard/index.md b/doc/user/group/security_dashboard/index.md
index 43e910b29fe..c59198df081 100644
--- a/doc/user/group/security_dashboard/index.md
+++ b/doc/user/group/security_dashboard/index.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html'
+redirect_to: '../../application_security/security_dashboard/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html).
+This document was moved to [another location](../../application_security/security_dashboard/index.md).
diff --git a/doc/user/index.md b/doc/user/index.md
index 67d5cbf06ec..1fc4e4c43cf 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -49,22 +49,22 @@ GitLab is a Git-based platform that integrates a great number of essential tools
With GitLab Enterprise Edition, you can also:
-- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html).
+- Provide support with [Service Desk](project/service_desk.md).
- Improve collaboration with
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals-starter),
- [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+ [Merge Request Approvals](project/merge_requests/index.md#merge-request-approvals-starter),
+ [Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md),
and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter).
-- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).
-- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
-- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
-- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html).
+- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md).
+- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software.
+- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
+- [Authenticate users with Kerberos](../integration/kerberos.md).
- [Mirror a repository](../workflow/repository_mirroring.md) from elsewhere on your local server.
-- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html).
-- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html).
-- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts.
-- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
-- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html).
-- Scan your code for vulnerabilities and [display them in merge requests](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
+- [Export issues as CSV](project/issues/csv_export.md).
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipeline_graphs.md).
+- [Lock files](project/file_lock.md) to prevent conflicts.
+- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](project/deploy_boards.md).
+- Leverage continuous delivery method with [Canary Deployments](project/canary_deployments.md).
+- Scan your code for vulnerabilities and [display them in merge requests](application_security/sast/index.md).
You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 318053fdabb..a6e2f187090 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -36,91 +36,96 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
The following table depicts the various user permission levels in a project.
-| Action | Guest | Reporter | Developer |Maintainer| Owner |
-|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
-| Delete wiki pages | | | | ✓ | ✓ |
-| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Download project | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Assign issues | | ✓ | ✓ | ✓ | ✓ |
-| Assign merge requests | | | ✓ | ✓ | ✓ |
-| Label issues | | ✓ | ✓ | ✓ | ✓ |
-| Label merge requests | | | ✓ | ✓ | ✓ |
-| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
-| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
-| Manage labels | | ✓ | ✓ | ✓ | ✓ |
-| See a commit status | | ✓ | ✓ | ✓ | ✓ |
-| See a container registry | | ✓ | ✓ | ✓ | ✓ |
-| See environments | | ✓ | ✓ | ✓ | ✓ |
-| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
-| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
-| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
-| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
-| Pull from [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ |
-| Publish to [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | | ✓ | ✓ | ✓ |
-| Lock merge request discussions | | | ✓ | ✓ | ✓ |
-| Create new environments | | | ✓ | ✓ | ✓ |
-| Stop environments | | | ✓ | ✓ | ✓ |
-| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
-| Create new merge request | | | ✓ | ✓ | ✓ |
-| Create new branches | | | ✓ | ✓ | ✓ |
-| Push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Remove non-protected branches | | | ✓ | ✓ | ✓ |
-| Add tags | | | ✓ | ✓ | ✓ |
-| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
-| Create or update commit status | | | ✓ | ✓ | ✓ |
-| Update a container registry | | | ✓ | ✓ | ✓ |
-| Remove a container registry image | | | ✓ | ✓ | ✓ |
-| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
+| Action | Guest | Reporter | Developer |Maintainer| Owner |
+|---------------------------------------------------|---------|------------|-------------|----------|--------|
+| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View approved/blacklisted licenses **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Apply code change suggestions | | | ✓ | ✓ | ✓ |
-| Use environment terminals | | | | ✓ | ✓ |
-| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
-| Add new team members | | | | ✓ | ✓ |
-| Push to protected branches | | | | ✓ | ✓ |
-| Enable/disable branch protection | | | | ✓ | ✓ |
-| Turn on/off protected branch push for devs| | | | ✓ | ✓ |
-| Enable/disable tag protections | | | | ✓ | ✓ |
-| Rewrite/remove Git tags | | | | ✓ | ✓ |
-| Edit project | | | | ✓ | ✓ |
-| Add deploy keys to project | | | | ✓ | ✓ |
-| Configure project hooks | | | | ✓ | ✓ |
-| Manage Runners | | | | ✓ | ✓ |
-| Manage job triggers | | | | ✓ | ✓ |
-| Manage variables | | | | ✓ | ✓ |
-| Manage GitLab Pages | | | | ✓ | ✓ |
-| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
-| Remove GitLab Pages | | | | ✓ | ✓ |
+| View license management reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Security reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Manage clusters | | | | ✓ | ✓ |
-| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
-| Edit comments (posted by any user) | | | | ✓ | ✓ |
-| Manage Error Tracking | | | | ✓ | ✓ |
-| Switch visibility level | | | | | ✓ |
-| Transfer project to another namespace | | | | | ✓ |
-| Remove project | | | | | ✓ |
-| Delete issues | | | | | ✓ |
-| Force push to protected branches [^4] | | | | | |
-| Remove protected branches [^4] | | | | | |
-| View project Audit Events | | | | ✓ | ✓ |
-| View project statistics | | ✓ | ✓ | ✓ | ✓ |
-| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View wiki pages | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See a list of jobs | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Create new issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
+| Assign issues | | ✓ | ✓ | ✓ | ✓ |
+| Label issues | | ✓ | ✓ | ✓ | ✓ |
+| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
+| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
+| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
+| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
+| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
+| See environments | | ✓ | ✓ | ✓ | ✓ |
+| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
+| View project statistics | | ✓ | ✓ | ✓ | ✓ |
+| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
+| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ |
+| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | | ✓ | ✓ | ✓ ||
+| Create new branches | | | ✓ | ✓ | ✓ |
+| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Remove non-protected branches | | | ✓ | ✓ | ✓ |
+| Create new merge request | | | ✓ | ✓ | ✓ |
+| Assign merge requests | | | ✓ | ✓ | ✓ |
+| Label merge requests | | | ✓ | ✓ | ✓ |
+| Lock merge request discussions | | | ✓ | ✓ | ✓ |
+| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
+| Create new environments | | | ✓ | ✓ | ✓ |
+| Stop environments | | | ✓ | ✓ | ✓ |
+| Add tags | | | ✓ | ✓ | ✓ |
+| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
+| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
+| Remove a container registry image | | | ✓ | ✓ | ✓ |
+| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
+| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Apply code change suggestions | | | ✓ | ✓ | ✓ |
+| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
+| Use environment terminals | | | | ✓ | ✓ |
+| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
+| Add new team members | | | | ✓ | ✓ |
+| Enable/disable branch protection | | | | ✓ | ✓ |
+| Push to protected branches | | | | ✓ | ✓ |
+| Turn on/off protected branch push for devs | | | | ✓ | ✓ |
+| Enable/disable tag protections | | | | ✓ | ✓ |
+| Rewrite/remove Git tags | | | | ✓ | ✓ |
+| Edit project | | | | ✓ | ✓ |
+| Add deploy keys to project | | | | ✓ | ✓ |
+| Configure project hooks | | | | ✓ | ✓ |
+| Manage Runners | | | | ✓ | ✓ |
+| Manage job triggers | | | | ✓ | ✓ |
+| Manage variables | | | | ✓ | ✓ |
+| Manage GitLab Pages | | | | ✓ | ✓ |
+| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
+| Remove GitLab Pages | | | | ✓ | ✓ |
+| Manage clusters | | | | ✓ | ✓ |
+| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
+| Edit comments (posted by any user) | | | | ✓ | ✓ |
+| Manage Error Tracking | | | | ✓ | ✓ |
+| Delete wiki pages | | | | ✓ | ✓ |
+| View project Audit Events | | | | ✓ | ✓ |
+| Switch visibility level | | | | | ✓ |
+| Transfer project to another namespace | | | | | ✓ |
+| Remove project | | | | | ✓ |
+| Delete issues | | | | | ✓ |
+| Force push to protected branches [^4] | | | | | |
+| Remove protected branches [^4] | | | | | |
+
+- (*1*): All users are able to perform this action on public and internal projects, but not private projects.
+- (*2*): Guest users can only view the confidential issues they created themselves
+- (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**
+- (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
## Project features permissions
@@ -163,7 +168,7 @@ to learn more.
The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
-Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more.
+Read through the documentation on [permissions for File Locking](project/file_lock.md#permissions-on-file-locking) to learn more.
### Confidential Issues permissions
@@ -191,21 +196,21 @@ Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
-| Action | Guest | Reporter | Developer | Maintainer | Owner |
-|-------------------------|-------|----------|-----------|--------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Edit group | | | | | ✓ |
-| Create subgroup | | | | | ✓ |
-| Create project in group | | | ✓ | ✓ | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| Delete group epic **[ULTIMATE]** | | | | | ✓ |
-| View group Audit Events | | | | | ✓ |
-| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Action | Guest | Reporter | Developer | Maintainer | Owner |
+|---------------------------------------|-------|----------|-----------|------------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
+| Create project in group | | | ✓ | ✓ | ✓ |
+| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Edit group | | | | | ✓ |
+| Create subgroup | | | | | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+| Delete group epic **[ULTIMATE]** | | | | | ✓ |
+| View group Audit Events | | | | | ✓ |
### Subgroup permissions
@@ -257,15 +262,15 @@ Please be aware that this regex could lead to a DOS attack, [see](https://en.wik
## Auditor users **[PREMIUM ONLY]**
->[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
+>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.17.
Auditor users are given read-only access to all projects, groups, and other
resources on the GitLab instance.
An Auditor user should be able to access all projects and groups of a GitLab instance
-with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user).
+with the permissions described on the documentation on [auditor users permissions](../administration/auditor_users.md#permissions-and-restrictions-of-an-auditor-user).
-[Read more about Auditor users.](https://docs.gitlab.com/ee/administration/auditor_users.html)
+[Read more about Auditor users.](../administration/auditor_users.md)
## Project features
@@ -298,7 +303,7 @@ instance and project. In addition, all admins can use the admin interface under
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel job | | ✓ | ✓ | ✓ |
-| Erase job artifacts and trace | | ✓ [^5] | ✓ | ✓ |
+| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
@@ -307,6 +312,8 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
+- *1*: Only if the job was triggered by the user
+
### Job permissions
NOTE: **Note:**
@@ -314,25 +321,28 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
>**Note:**
GitLab 8.12 has a completely redesigned job permissions system.
-Read all about the [new model and its implications][new-mod].
+Read all about the [new model and its implications](project/new_ci_build_permissions_model.md).
This table shows granted privileges for jobs triggered by specific types of
users:
-| Action | Guest, Reporter | Developer |Maintainer| Admin |
-|---------------------------------------------|-----------------|-------------|----------|--------|
-| Run CI job | | ✓ | ✓ | ✓ |
-| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
-| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
-| Clone source and LFS from internal projects | | ✓ [^6] | ✓ [^6] | ✓ |
-| Clone source and LFS from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push source and LFS | | | | |
-| Pull container images from current project | | ✓ | ✓ | ✓ |
-| Pull container images from public projects | | ✓ | ✓ | ✓ |
-| Pull container images from internal projects| | ✓ [^6] | ✓ [^6] | ✓ |
-| Pull container images from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push container images to current project | | ✓ | ✓ | ✓ |
-| Push container images to other projects | | | | |
+| Action | Guest, Reporter | Developer |Maintainer| Admin |
+|---------------------------------------------|-----------------|-------------|----------|---------|
+| Run CI job | | ✓ | ✓ | ✓ |
+| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
+| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
+| Clone source and LFS from internal projects | | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Clone source and LFS from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Pull container images from current project | | ✓ | ✓ | ✓ |
+| Pull container images from public projects | | ✓ | ✓ | ✓ |
+| Pull container images from internal projects| | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Pull container images from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Push container images to current project | | ✓ | ✓ | ✓ |
+| Push container images to other projects | | | | |
+| Push source and LFS | | | | |
+
+- *1*: Only if the user is not an external one
+- *2*: Only if the user is a member of the project
### New CI job permissions model
@@ -350,17 +360,4 @@ for details about the pipelines security model.
## LDAP users permissions
Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
-Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
-
-[^1]: On public and internal projects, all users are able to perform this action
-[^2]: Guest users can only view the confidential issues they created themselves
-[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD**
-[^4]: Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
-[^5]: Only if the job was triggered by the user
-[^6]: Only if user is not external one
-[^7]: Only if user is a member of the project
-
-[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
-[new-mod]: project/new_ci_build_permissions_model.md
-[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998
-[eep]: https://about.gitlab.com/pricing/
+Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 5166524d13b..0b224fc7e01 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -37,20 +37,18 @@ Personal access tokens can be created with one or more scopes that allow various
actions that a given token can perform. The available scopes are depicted in
the following table.
-| Scope | Description |
-| ----- | ----------- |
-|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
-| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). |
-| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). |
-| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
-| `read_repository` | Allows read-only access (pull) to the repository through git clone. |
-| `write_repository` | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. |
+| Scope | Introduced in | Description |
+| ------------------ | ------------- | ----------- |
+| `read_user` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed. |
+| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete access to the API and Container Registry (read/write). |
+| `read_registry` | [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) | Allows to read (pull) [container registry] images if a project is private and authorization is required. |
+| `sudo` | [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838) | Allows performing API actions as any user in the system (if the authenticated user is an admin). |
+| `read_repository` | [GitLab 10.7](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894) | Allows read-only access (pull) to the repository through git clone. |
+| `write_repository` | [GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26021) | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
-[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
-[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#personal-access-tokens
diff --git a/doc/user/project/ci_cd_for_external_repo.md b/doc/user/project/ci_cd_for_external_repo.md
index 51b86a68c7b..a92d3a2c308 100644
--- a/doc/user/project/ci_cd_for_external_repo.md
+++ b/doc/user/project/ci_cd_for_external_repo.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html'
+redirect_to: '../../ci/ci_cd_for_external_repos/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html).
+This document was moved to [another location](../../ci/ci_cd_for_external_repos/index.md).
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index e38e4059117..dc21db603d6 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -450,7 +450,7 @@ differentiate the new cluster with the rest.
When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-environment-variables-premium) work.
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
The default environment scope is `*`, which means all jobs, regardless of their
environment, will use that cluster. Each scope can only be used by a single
@@ -588,7 +588,7 @@ displaying the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
-[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+[Read more about Deploy Boards](../deploy_boards.md)
### Canary Deployments **[PREMIUM]**
@@ -596,7 +596,7 @@ Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cl
and visualize your canary deployments right inside the Deploy Board, without
the need to leave GitLab.
-[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+[Read more about Canary Deployments](../canary_deployments.md)
### Pod logs **[ULTIMATE]**
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index d5b60250860..368031070c1 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -7,10 +7,10 @@ By displaying the logs directly in GitLab, developers can avoid having to manage
## Overview
-[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html):
+[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`.
-1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
+1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md).
1. When mousing over the list of pods, a tooltip will appear with the exact pod name and status.
![Deploy Boards pod list](img/pod_logs_deploy_board.png)
1. Click on the desired pod to bring up the logs view, which will contain the last 500 lines for that pod. Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab-ee/issues/6502).
@@ -18,4 +18,4 @@ By displaying the logs directly in GitLab, developers can avoid having to manage
## Requirements
-[Enabling Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html#enabling-deploy-boards) is required in order to be able to use Pod Logs.
+[Enabling Deploy Boards](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs.
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index 54c475a1762..6360a01a0ad 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -34,7 +34,7 @@ for an overview of how this is accomplished in GitLab!**
To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
all the other applications. It is installed in its own pod inside the cluster which
can run the helm CLI in a safe environment.
@@ -59,7 +59,7 @@ the components outlined above and the preloaded demo runbook.
### 1. Add a Kubernetes cluster
-Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab)
+Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
@@ -90,7 +90,7 @@ The server will take a couple of seconds to start.
### 4. Configure access
In order for the runbook to access your GitLab project, you will need to enter a
-[GitLab Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
+[GitLab Access Token](../../../profile/personal_access_tokens.md)
as well as your Project ID in the **Setup** section of the demo runbook.
Double-click the **DevOps-Runbook-Demo** folder located on the left panel.
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 7688508c6ac..92a29b68a22 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -5,7 +5,7 @@
Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password.
Please note, that the expiration of deploy tokens happens on the date you define,
-at midnight UTC and that they can be only managed by [maintainers](https://docs.gitlab.com/ee/user/permissions.html).
+at midnight UTC and that they can be only managed by [maintainers](../../permissions.md).
## Creating a Deploy Token
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index dc5b3fcd0bb..7f79ebf6353 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -9,9 +9,9 @@ Gemnasium has been [acquired by GitLab](https://about.gitlab.com/press/releases/
in January 2018. Since May 15, 2018, the services provided by Gemnasium are no longer available.
The team behind Gemnasium has joined GitLab as the new Security Products team
and is working on a wider range of tools than just Dependency Scanning:
-[SAST](https://docs.gitlab.com/ee/user/application_security/sast/index.html),
-[DAST](https://docs.gitlab.com/ee/user/application_security/dast/index.html),
-[Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html) and more.
+[SAST](../../application_security/sast/index.md),
+[DAST](../../application_security/dast/index.md),
+[Container Scanning](../../application_security/container_scanning/index.md) and more.
If you want to continue monitoring your dependencies, see the
[Migrating to GitLab](#migrating-to-gitlab) section below.
@@ -45,7 +45,7 @@ Security features are free for public (open-source) projects hosted on GitLab.co
You're almost set! If you're already using
[Auto DevOps](../../../topics/autodevops/), you are already covered.
Otherwise, you must configure your `.gitlab-ci.yml` according to the
-[dependency scanning page](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+[dependency scanning page](../../application_security/dependency_scanning/index.md).
### If your project is hosted on GitHub (https://github.com / GitHub Enterprise)
@@ -81,7 +81,7 @@ back to both GitLab and GitHub when completed.
1. To set up the dependency scanning job, corresponding to what Gemnasium was
doing, you must create a `.gitlab-ci.yml` file, or update it according to
- the [dependency scanning docs](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+ the [dependency scanning docs](../../application_security/dependency_scanning/index.md).
The mirroring is pull-only by default, so you may create or update the file on
GitHub:
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 63b90dd76fd..8fba892594b 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -119,9 +119,9 @@ Depending your GitLab tier, [project mirroring](../../../workflow/repository_mir
your imported project in sync with its GitHub copy.
Additionally, you can configure GitLab to send pipeline status updates back GitHub with the
-[GitHub Project Integration](https://docs.gitlab.com/ee/user/project/integrations/github.html). **[PREMIUM]**
+[GitHub Project Integration](../integrations/github.md). **[PREMIUM]**
-If you import your project using [CI/CD for external repo](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/), then both
+If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/index.md), then both
of the above are automatically configured. **[PREMIUM]**
## Improving the speed of imports on self-hosted instances
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index ebbc5ca133b..2b6927bd780 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -14,12 +14,13 @@
1. [From repo by URL](repo_by_url.md)
1. [By uploading a manifest file (AOSP)](manifest.md)
1. [From Gemnasium](gemnasium.md)
+1. [From Phabricator](phabricator.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
repository is too large the import can timeout.
-There is also the option of [connecting your external repository to get CI/CD benefits](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]**
+There is also the option of [connecting your external repository to get CI/CD benefits](../../../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
## Migrating from self-hosted GitLab to GitLab.com
diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md
new file mode 100644
index 00000000000..4d1d99fd35b
--- /dev/null
+++ b/doc/user/project/import/phabricator.md
@@ -0,0 +1,32 @@
+# Import Phabricator tasks into a GitLab project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60562) in
+GitLab 12.0.
+
+GitLab allows you to import all tasks from a Phabricator instance into
+GitLab issues. The import creates a single project with the
+repository disabled.
+
+Currently, only the following basic fields are imported:
+
+- Title
+- Description
+- State (open or closed)
+- Created at
+- Closed at
+
+
+## Enabling this feature
+
+While this feature is incomplete, a feature flag is required to enable it so that
+we can gain early feedback before releasing it for everyone. To enable it:
+
+1. Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin area.
+
+ ``` {.ruby}
+ Feature.enable(:phabricator_import)
+ ```
+
+The [import
+source](../../admin_area/settings/visibility_and_access_controls.md#import-sources)
+also needs to be activated by an admin in the admin interface.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 6b3b40bf9f8..a24f525253d 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -37,7 +37,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
+ - [Merge Request Approvals](merge_requests/merge_request_approvals.md): Ask for approval before
implementing a change **[STARTER]**
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI
@@ -74,7 +74,7 @@ When you create a project in GitLab, you'll have access to a large number of
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
- - [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html): Feature flags allow you to ship a project in
+ - [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in
different flavors by dynamically toggling certain functionality **[PREMIUM]**
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
@@ -91,10 +91,10 @@ When you create a project in GitLab, you'll have access to a large number of
- [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of
the source, build output, and other metadata or artifacts
associated with a released version of your code.
-- [Maven packages](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html): your private Maven repository in GitLab. **[PREMIUM]**
-- [NPM packages](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html): your private NPM package registry in GitLab. **[PREMIUM]**
+- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **[PREMIUM]**
+- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **[PREMIUM]**
- [Code owners](code_owners.md): specify code owners for certain files **[STARTER]**
-- [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.html): approve and blacklist licenses for projects. **[ULTIMATE]**
+- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **[ULTIMATE]**
### Project integrations
@@ -135,7 +135,7 @@ Read through the documentation on [project settings](settings/index.md).
Instead of importing a repository directly to GitLab, you can connect your repository
as a CI/CD project.
-Read through the documentation on [CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html).
+Read through the documentation on [CI/CD for external repositories](../../ci/ci_cd_for_external_repos/index.md).
## Project members
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
index b6cc1862cc2..5154ff38154 100644
--- a/doc/user/project/insights/index.md
+++ b/doc/user/project/insights/index.md
@@ -14,7 +14,7 @@ requests to be merged and much more.
![Insights example bar chart](img/project_insights.png)
NOTE: **Note:**
-This feature is [also available at the group level](https://docs.gitlab.com/ee/user/group/insights/index.html).
+This feature is [also available at the group level](../../group/insights/index.md).
## View your project's Insights
@@ -33,7 +33,7 @@ for details about the content of this file.
NOTE: **Note:**
Once the configuration file is created, you can also
-[use it for your project's group](https://docs.gitlab.com/ee/user/group/insights/index.html#configure-your-insights).
+[use it for your project's group](../../group/insights/index.md#configure-your-insights).
NOTE: **Note:**
If the project doesn't have any configuration file, it'll try to use
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index f560de427c5..0bfee3bac99 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -39,7 +39,7 @@ Click on the service links to see further configuration instructions and details
| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
| [JIRA](jira.md) | JIRA issue tracker |
-| [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html) **[STARTER]** | An extendable open source continuous integration server |
+| [Jenkins](../../../integration/jenkins.md) **[STARTER]** | An extendable open source continuous integration server |
| JetBrains TeamCity CI | A continuous integration and build server |
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 436129f1dbc..8b1cf1a251a 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -27,7 +27,7 @@ integration services must be enabled.
Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so:
-- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
+- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.md#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/).
## Specifying the Environment
@@ -40,7 +40,7 @@ Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controll
> Introduced in [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15201).
-GitLab also gathers Kubernetes metrics for [canary deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html), allowing easy comparison between the current deployed version and the canary.
+GitLab also gathers Kubernetes metrics for [canary deployments](../../canary_deployments.md), allowing easy comparison between the current deployed version and the canary.
These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name to begin with `$CI_ENVIRONMENT_SLUG-canary`, to isolate the canary metrics.
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index 5e05846b77f..c2916c79876 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -67,7 +67,7 @@ or contacts to continue working._
## New issue via Service Desk **[PREMIUM]**
-Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) to your project and offer email support.
+Enable [Service Desk](../service_desk.md) to your project and offer email support.
By doing so, when your customer sends a new email, a new issue can be created in
the appropriate project and followed up from there.
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index c82b7f100d2..94865ad46ee 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -102,13 +102,13 @@ For more information, see the [Issue Boards](../issue_board.md) page.
Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones.
-For more information, see the [Epics](https://docs.gitlab.com/ee/user/group/epics/) page.
+For more information, see the [Epics](../../group/epics/index.md) page.
### Related issues **[STARTER]**
You can mark two issues as related, so that when viewing each one, the other is always listed in its Related Issues section. This can help display important context, such as past work, dependencies, or duplicates.
-For more information, see [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).
+For more information, see [Related Issues](related_issues.md).
### Crosslinking issues
@@ -129,7 +129,7 @@ For more information, see [Crosslinking issues](crosslinking_issues.md).
- [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues in order to change their status, assignee, milestone, or labels in bulk.
- [Import issues](csv_import.md)
-- [Export issues](https://docs.gitlab.com/ee/user/project/issues/csv_export.html) **[STARTER]**
+- [Export issues](csv_export.md) **[STARTER]**
- [Issues API](../../../api/issues.md)
- Configure an [external issue tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index ef9fcaec3e6..fc11c0251e0 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -90,7 +90,7 @@ If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown me
- Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed.
-Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html).
+Learn more in the [Issue Weight documentation](../../../workflow/issue_weight.md).
#### 9. Participants
@@ -103,7 +103,7 @@ Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workfl
- Unsubscribe: if you are receiving notifications on that issue but no
longer want to receive them, unsubscribe from it.
-Read more in the [notifications documentation](https://docs.gitlab.com/ee/workflow/notifications.html#issue--epics--merge-request-events).
+Read more in the [notifications documentation](../../../workflow/notifications.md#issue--epics--merge-request-events).
#### 11. Reference
diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md
index db0ab65b442..e679ebf86e6 100644
--- a/doc/user/project/issues/related_issues.md
+++ b/doc/user/project/issues/related_issues.md
@@ -1,6 +1,6 @@
# Related issues **[STARTER]**
-> [Introduced][ee-1797] in [GitLab Starter][ee] 9.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.4.
Related issues are a bi-directional relationship between any two issues
and appear in a block below the issue description. Issues can be across groups
@@ -35,11 +35,6 @@ will no longer appear in either issue.
![Removing a related issue](img/related_issues_remove.png)
-Please access our [permissions] page for more information.
+Please access our [permissions](../../permissions.md) page for more information.
-Additionally, you are also able to manage related issues through [our API].
-
-[ee]: https://about.gitlab.com/pricing/
-[ee-1797]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797
-[permissions]: ../../permissions.md
-[Our API]: https://docs.gitlab.com/ee/api/issue_links.html
+Additionally, you are also able to manage related issues through [our API](../../../api/issue_links.md).
diff --git a/doc/user/project/maven_packages.md b/doc/user/project/maven_packages.md
index d32d6084b38..48835a2dac7 100644
--- a/doc/user/project/maven_packages.md
+++ b/doc/user/project/maven_packages.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/project/packages/maven_repository.html'
+redirect_to: 'packages/maven_repository.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html).
+This document was moved to [another location](packages/maven_repository.md).
diff --git a/doc/user/project/merge_requests/code_quality_diff.md b/doc/user/project/merge_requests/code_quality_diff.md
index 890058eec6f..ccc694672a6 100644
--- a/doc/user/project/merge_requests/code_quality_diff.md
+++ b/doc/user/project/merge_requests/code_quality_diff.md
@@ -1,5 +1,5 @@
---
-redirect_from: 'https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html'
+redirect_from: 'code_quality_diff.md'
redirect_to: 'code_quality.md'
---
diff --git a/doc/user/project/merge_requests/container_scanning.md b/doc/user/project/merge_requests/container_scanning.md
index 4d41e424f4a..a062731ea35 100644
--- a/doc/user/project/merge_requests/container_scanning.md
+++ b/doc/user/project/merge_requests/container_scanning.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+redirect_to: '../../application_security/container_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/merge_requests/dast.md b/doc/user/project/merge_requests/dast.md
index b676c661267..98a2906e560 100644
--- a/doc/user/project/merge_requests/dast.md
+++ b/doc/user/project/merge_requests/dast.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dast/index.html'
+redirect_to: '../../application_security/dast/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dast/index.html).
+This document was moved to [another location](../../application_security/dast/index.md).
diff --git a/doc/user/project/merge_requests/dependency_scanning.md b/doc/user/project/merge_requests/dependency_scanning.md
index 3a8b53b425c..bdc1c355016 100644
--- a/doc/user/project/merge_requests/dependency_scanning.md
+++ b/doc/user/project/merge_requests/dependency_scanning.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html'
+redirect_to: '../../application_security/dependency_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+This document was moved to [another location](../../application_security/dependency_scanning/index.md).
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 09736c7fc7e..4cfe59b808a 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -33,15 +33,15 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also:
-- Prepare a full review and submit it once it's ready with [Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.md#merge-request-reviews-premium) **[PREMIUM]**
-- View the deployment process across projects with [Multi-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipelines.md) **[PREMIUM]**
+- Prepare a full review and submit it once it's ready with [Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium) **[PREMIUM]**
+- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **[PREMIUM]**
- Request [approvals](merge_request_approvals.md) from your managers **[STARTER]**
- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **[STARTER]**
-- Manage the licenses of your dependencies with [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.md) **[ULTIMATE]**
-- Analyze your source code for vulnerabilities with [Static Application Security Testing](https://docs.gitlab.com/ee/user/application_security/sast/index.md) **[ULTIMATE]**
-- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](https://docs.gitlab.com/ee/user/application_security/dast/index.md) **[ULTIMATE]**
-- Analyze your dependencies for vulnerabilities with [Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.md) **[ULTIMATE]**
-- Analyze your Docker images for vulnerabilities with [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.md) **[ULTIMATE]**
+- Manage the licenses of your dependencies with [License Management](../../application_security/license_management/index.md) **[ULTIMATE]**
+- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **[ULTIMATE]**
+- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **[ULTIMATE]**
+- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **[ULTIMATE]**
+- Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **[ULTIMATE]**
- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **[PREMIUM]**
## Use cases
@@ -174,7 +174,7 @@ Start a review in order to create multiple comments on a diff and publish them o
Starting a review allows you to get all your thoughts in order and ensure you haven't missed anything
before submitting all your comments.
-[Learn more about Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.html#merge-request-reviews-premium)
+[Learn more about Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium)
## Squash and merge
@@ -395,7 +395,7 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d
GitLab can scan and report any vulnerabilities found in your project.
-[Read more about security reports.](https://docs.gitlab.com/ee/user/application_security/index.html)
+[Read more about security reports.](../../application_security/index.md)
## JUnit test reports
diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md
index 08704425a75..93116ebd7c6 100644
--- a/doc/user/project/merge_requests/license_management.md
+++ b/doc/user/project/merge_requests/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/license_management/index.html'
+redirect_to: '../../application_security/license_management/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/license_management/index.html).
+This document was moved to [another location](../../application_security/license_management/index.md).
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index d0291c4cef5..52b6b56af84 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -105,7 +105,7 @@ any [eligible approver](#eligible-approvers) may approve.
The following can approve merge requests:
- Users being added as approvers at project or merge request level.
-- [Code owners](https://docs.gitlab.com/ee/user/project/code_owners.html) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5).
+- [Code owners](../code_owners.md) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5).
An individual user can be added as an approver for a project if they are a member of:
@@ -168,7 +168,7 @@ or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4418) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.9.
It is possible to require at least one approval for each entry in the
-[`CODEOWNERS` file](https://docs.gitlab.com/ee/user/project/code_owners.html) that matches a file changed in
+[`CODEOWNERS` file](../code_owners.md) that matches a file changed in
the merge request. To enable this feature:
1. Navigate to your project's **Settings > General** and expand
diff --git a/doc/user/project/merge_requests/sast.md b/doc/user/project/merge_requests/sast.md
index 688cc79d0f6..165290eb114 100644
--- a/doc/user/project/merge_requests/sast.md
+++ b/doc/user/project/merge_requests/sast.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/sast/index.html'
+redirect_to: '../../application_security/sast/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
+This document was moved to [another location](../../application_security/sast/index.md).
diff --git a/doc/user/project/merge_requests/sast_docker.md b/doc/user/project/merge_requests/sast_docker.md
index 4d41e424f4a..a062731ea35 100644
--- a/doc/user/project/merge_requests/sast_docker.md
+++ b/doc/user/project/merge_requests/sast_docker.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+redirect_to: '../../application_security/container_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/milestones/burndown_charts.md b/doc/user/project/milestones/burndown_charts.md
index 0ad08da8ff5..7ffeb032d7f 100644
--- a/doc/user/project/milestones/burndown_charts.md
+++ b/doc/user/project/milestones/burndown_charts.md
@@ -52,7 +52,7 @@ and select a milestone from your current ones, while for group's, access the **G
select a group, and go through **Issues > Milestones** on the sidebar.
NOTE: **Note:**
-You're able to [promote project](https://docs.gitlab.com/ee/user/project/milestones/#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations.
+You're able to [promote project](index.md#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations.
The chart indicates the project's progress throughout that milestone (for issues assigned to it).
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
index 94785eb6aec..9b7af738696 100644
--- a/doc/user/project/packages/maven_repository.md
+++ b/doc/user/project/packages/maven_repository.md
@@ -12,7 +12,7 @@ project can have its own space to store its Maven artifacts.
NOTE: **Note:**
This option is available only if your GitLab administrator has
-[enabled support for the Maven repository](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]**
+[enabled support for the Maven repository](../../../administration/packages.md).**[PREMIUM ONLY]**
After the Packages feature is enabled, the Maven Repository will be available for
all new projects by default. To enable it for existing projects, or if you want
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index 9f4c01c9a0a..2e274573434 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -20,7 +20,7 @@ within a subgroup is not supported yet.
NOTE: **Note:**
This option is available only if your GitLab administrator has
-[enabled support for the NPM registry](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]**
+[enabled support for the NPM registry](../../../administration/packages.md).**[PREMIUM ONLY]**
After the NPM registry is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index cb514b76a4e..6fccfd40987 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -123,7 +123,7 @@ You can live preview changes submitted to a new branch with
[Review Apps](../../../ci/review_apps/index.md).
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
-[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers.
+[approval](../merge_requests/merge_request_approvals.md) from your managers.
To create, delete, and view [branches](branches/index.md) via GitLab's UI:
@@ -154,7 +154,7 @@ Via command line, you can commit multiple times before pushing.
you will trigger a pipeline per push, not per commit.
- **Skip pipelines:**
You can add to you commit message the keyword
- [`[ci skip]`](../../../ci/yaml/README.html#skipping-jobs)
+ [`[ci skip]`](../../../ci/yaml/README.md#skipping-jobs)
and GitLab CI will skip that pipeline.
- **Cross-link issues and merge requests:**
[Cross-linking](../issues/crosslinking_issues.md#from-commit-messages)
@@ -226,7 +226,7 @@ Find it under your project's **Repository > Compare**.
## Locked files **[PREMIUM]**
-Use [File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) to
+Use [File Locking](../file_lock.md) to
lock your files to prevent any conflicting changes.
## Repository's API
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 2339759ecc8..e3d771524ce 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,6 +1,6 @@
# Reducing the repository size using Git
-A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size]
+A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md)
which will prevent you from exceeding it.
When a project has reached its size limit, you will not be able to push to it,
@@ -14,7 +14,8 @@ move some blobs to LFS, or remove some old dependency updates from history.
Unfortunately, it's not so easy and that workflow won't work. Deleting files in
a commit doesn't actually reduce the size of the repo since the earlier commits
and blobs are still around. What you need to do is rewrite history with Git's
-[`filter-branch` option][gitscm], or a tool like the [BFG Repo-Cleaner][bfg].
+[`filter-branch` option](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch),
+or a tool like the [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/).
Note that even with that method, until `git gc` runs on the GitLab side, the
"removed" commits and blobs will still be around. You also need to be able to
@@ -137,7 +138,3 @@ remove some of them, but it should not be depended on for security purposes!
```
Your repository should now be below the size limit.
-
-[admin-repo-size]: https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html#repository-size-limit
-[bfg]: https://rtyley.github.io/bfg-repo-cleaner/
-[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch
diff --git a/doc/user/project/security_dashboard.md b/doc/user/project/security_dashboard.md
index 43e910b29fe..a3da1ec97d3 100644
--- a/doc/user/project/security_dashboard.md
+++ b/doc/user/project/security_dashboard.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html'
+redirect_to: '../application_security/security_dashboard/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html).
+This document was moved to [another location](../application_security/security_dashboard/index.md).
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 737dea1de6c..e3502a632d9 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -36,7 +36,7 @@ Set up your project's merge request settings:
- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)).
- Merge request [description templates](../description_templates.md#description-templates).
-- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]**
+- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **[STARTER]**
- Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
@@ -44,7 +44,7 @@ Set up your project's merge request settings:
### Service Desk **[PREMIUM]**
-Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support.
+Enable [Service Desk](../service_desk.md) for your project to offer customer support.
### Export project
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
index 38b26f31417..f80f4183802 100644
--- a/doc/user/search/advanced_global_search.md
+++ b/doc/user/search/advanced_global_search.md
@@ -2,7 +2,7 @@
> - [Introduced][ee-109] in GitLab [Starter][ee] 8.4.
> - This is the user documentation. To install and configure Elasticsearch,
-> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html).
+> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index 8d4aac5f502..d302cb7a809 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -3,7 +3,7 @@
> **Notes:**
> - Introduced in [GitLab Enterprise Starter][ee] 9.2
> - This is the user documentation. To install and configure Elasticsearch,
-> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html).
+> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png
index 8a5494d16a8..72f7418f1f8 100644
--- a/doc/workflow/img/notification_global_settings.png
+++ b/doc/workflow/img/notification_global_settings.png
Binary files differ
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 0eb4f85e0ec..5d560f2e000 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -16,12 +16,16 @@ Notification settings are divided into three groups:
Each of these settings have levels of notification:
+- Global: For groups and projects, notifications as per global settings.
- Watch: Receive notifications for any activity.
-- On Mention: Receive notifications when `@mentioned` in comments.
- Participate: Receive notifications for threads you have participated in.
+- On Mention: Receive notifications when `@mentioned` in comments.
- Disabled: Turns off notifications.
- Custom: Receive notifications for custom selected events.
-- Global: For groups and projects, notifications as per global settings.
+
+> Introduced in GitLab 12.0
+
+You can also select an email address to receive notifications for each group you belong to.
### Global Settings
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 65eb9bfb87e..80913f4ca07 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -96,17 +96,27 @@ module API
end
end
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
end
post ':id/repository/commits' do
+ if params[:start_project]
+ start_project = find_project!(params[:start_project])
+
+ unless user_project.forked_from?(start_project)
+ forbidden!("Project is not included in the fork network for #{start_project.full_name}")
+ end
+ end
+
authorize_push_to_branch!(params[:branch])
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7e4539d0419..00bcf6b055b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -445,7 +445,7 @@ module API
end
def present_carrierwave_file!(file, supports_direct_download: true)
- return not_found! unless file.exists?
+ return not_found! unless file&.exists?
if file.file_storage?
present_disk_file!(file.path, file.filename)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 455588f3c66..dcdd3581d92 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -34,34 +34,6 @@ module Gitlab
TagExistsError = Class.new(StandardError)
ChecksumError = Class.new(StandardError)
- class << self
- def create_hooks(repo_path, global_hooks_path)
- local_hooks_path = File.join(repo_path, 'hooks')
- real_local_hooks_path = :not_found
-
- begin
- real_local_hooks_path = File.realpath(local_hooks_path)
- rescue Errno::ENOENT
- # real_local_hooks_path == :not_found
- end
-
- # Do nothing if hooks already exist
- unless real_local_hooks_path == File.realpath(global_hooks_path)
- if File.exist?(local_hooks_path)
- # Move the existing hooks somewhere safe
- FileUtils.mv(
- local_hooks_path,
- "#{local_hooks_path}.old.#{Time.now.to_i}")
- end
-
- # Create the hooks symlink
- FileUtils.ln_sf(global_hooks_path, local_hooks_path)
- end
-
- true
- end
- end
-
# Directory name of repo
attr_reader :name
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index 9d81441d96e..1d3541b80c7 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -29,29 +29,13 @@ module Gitlab
end
def execute
- jid = generate_jid
-
- # The original import JID is the JID of the RepositoryImportWorker job,
- # which will be removed once that job completes. Reusing that JID could
- # result in StuckImportJobsWorker marking the job as stuck before we get
- # to running Stage::ImportRepositoryWorker.
- #
- # We work around this by setting the JID to a custom generated one, then
- # refreshing it in the various stages whenever necessary.
- Gitlab::SidekiqStatus
- .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
-
- project.import_state.update_column(:jid, jid)
+ Gitlab::Import::SetAsyncJid.set_jid(project)
Stage::ImportRepositoryWorker
.perform_async(project.id)
true
end
-
- def generate_jid
- "github-importer/#{project.id}"
- end
end
end
end
diff --git a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb b/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb
deleted file mode 100644
index 95db130dbfc..00000000000
--- a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- module QueryAnalyzers
- class LogQueryComplexity
- class << self
- def analyzer
- GraphQL::Analysis::QueryComplexity.new do |query, complexity|
- # temporary until https://gitlab.com/gitlab-org/gitlab-ce/issues/59587
- Rails.logger.info("[GraphQL Query Complexity] #{complexity} | admin? #{query.context[:current_user]&.admin?}")
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
new file mode 100644
index 00000000000..01b55a1667f
--- /dev/null
+++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module QueryAnalyzers
+ class LoggerAnalyzer
+ COMPLEXITY_ANALYZER = GraphQL::Analysis::QueryComplexity.new { |query, complexity_value| complexity_value }
+ DEPTH_ANALYZER = GraphQL::Analysis::QueryDepth.new { |query, depth_value| depth_value }
+
+ def analyze?(query)
+ Feature.enabled?(:graphql_logging, default_enabled: true)
+ end
+
+ def initial_value(query)
+ variables = process_variables(query.provided_variables)
+ default_initial_values(query).merge({
+ query_string: query.query_string,
+ variables: variables
+ })
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ default_initial_values(query)
+ end
+
+ def call(memo, visit_type, irep_node)
+ memo
+ end
+
+ def final_value(memo)
+ return if memo.nil?
+
+ analyzers = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER]
+ complexity, depth = GraphQL::Analysis.analyze_query(memo[:query], analyzers)
+
+ memo[:depth] = depth
+ memo[:complexity] = complexity
+ memo[:duration] = duration(memo[:time_started]).round(1)
+
+ GraphqlLogger.info(memo.except!(:time_started, :query))
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ end
+
+ private
+
+ def process_variables(variables)
+ if variables.respond_to?(:to_s)
+ variables.to_s
+ else
+ variables
+ end
+ end
+
+ def duration(time_started)
+ nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started
+ nanoseconds * 1000000
+ end
+
+ def default_initial_values(query)
+ {
+ time_started: Gitlab::Metrics::System.monotonic_time,
+ query_string: nil,
+ query: query,
+ variables: nil,
+ duration: nil
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql_logger.rb b/lib/gitlab/graphql_logger.rb
new file mode 100644
index 00000000000..43d917908b6
--- /dev/null
+++ b/lib/gitlab/graphql_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GraphqlLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'graphql_json'
+ end
+ end
+end
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index bcd9e2be35f..313b5df51d4 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -9,6 +9,13 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
RedirectionTooDeep = Class.new(StandardError)
+ HTTP_ERRORS = [
+ SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET,
+ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout,
+ Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
+ Gitlab::HTTP::RedirectionTooDeep
+ ].freeze
+
include HTTParty # rubocop:disable Gitlab/HTTParty
connection_adapter ProxyHTTPConnectionAdapter
diff --git a/lib/gitlab/import/set_async_jid.rb b/lib/gitlab/import/set_async_jid.rb
new file mode 100644
index 00000000000..584d24045ee
--- /dev/null
+++ b/lib/gitlab/import/set_async_jid.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# The original import JID is the JID of the RepositoryImportWorker job,
+# which will be removed once that job completes. Reusing that JID could
+# result in StuckImportJobsWorker marking the job as stuck before we get
+# to running Stage::ImportRepositoryWorker.
+#
+# We work around this by setting the JID to a custom generated one, then
+# refreshing it in the various stages whenever necessary.
+module Gitlab
+ module Import
+ module SetAsyncJid
+ def self.set_jid(project)
+ jid = generate_jid(project)
+
+ Gitlab::SidekiqStatus
+ .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+
+ project.import_state.update_column(:jid, jid)
+ end
+
+ def self.generate_jid(project)
+ "async-import/#{project.id}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 67952ca0f2d..e4d625b5738 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -20,7 +20,8 @@ module Gitlab
ImportSource.new('git', 'Repo by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer),
- ImportSource.new('manifest', 'Manifest file', nil)
+ ImportSource.new('manifest', 'Manifest file', nil),
+ ImportSource.new('phabricator', 'Phabricator', Gitlab::PhabricatorImport::Importer)
].freeze
class << self
diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb
index 5501f7981ec..66aea137012 100644
--- a/lib/gitlab/lets_encrypt/client.rb
+++ b/lib/gitlab/lets_encrypt/client.rb
@@ -3,6 +3,8 @@
module Gitlab
module LetsEncrypt
class Client
+ include Gitlab::Utils::StrongMemoize
+
PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory'
STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
@@ -35,6 +37,8 @@ module Gitlab
def enabled?
return false unless Feature.enabled?(:pages_auto_ssl)
+ return false unless private_key
+
Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
end
@@ -45,7 +49,11 @@ module Gitlab
end
def private_key
- @private_key ||= OpenSSL::PKey.read(Gitlab::CurrentSettings.lets_encrypt_private_key)
+ strong_memoize(:private_key) do
+ private_key_string = Gitlab::CurrentSettings.lets_encrypt_private_key
+ private_key_string ||= generate_private_key
+ OpenSSL::PKey.read(private_key_string) if private_key_string
+ end
end
def admin_email
@@ -69,6 +77,19 @@ module Gitlab
STAGING_DIRECTORY_URL
end
end
+
+ def generate_private_key
+ return if Gitlab::Database.read_only?
+
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+ application_settings.with_lock do
+ unless application_settings.lets_encrypt_private_key
+ application_settings.update(lets_encrypt_private_key: OpenSSL::PKey::RSA.new(4096).to_pem)
+ end
+
+ application_settings.lets_encrypt_private_key
+ end
+ end
end
end
end
diff --git a/lib/gitlab/phabricator_import.rb b/lib/gitlab/phabricator_import.rb
new file mode 100644
index 00000000000..3885a9934d5
--- /dev/null
+++ b/lib/gitlab/phabricator_import.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ BaseError = Class.new(StandardError)
+
+ def self.available?
+ Feature.enabled?(:phabricator_import) &&
+ Gitlab::CurrentSettings.import_sources.include?('phabricator')
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb
new file mode 100644
index 00000000000..b69c65e78f8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/base_worker.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+# All workers within a Phabricator import should inherit from this worker and
+# implement the `#import` method. The jobs should then be scheduled using the
+# `.schedule` class method instead of `.perform_async`
+#
+# Doing this makes sure that only one job of that type is running at the same time
+# for a certain project. This will avoid deadlocks. When a job is already running
+# we'll wait for it for 10 times 5 seconds to restart. If the running job hasn't
+# finished, by then, we'll retry in 30 seconds.
+#
+# It also makes sure that we keep the import state of the project up to date:
+# - It keeps track of the jobs so we know how many jobs are running for the
+# project
+# - It refreshes the import jid, so it doesn't get cleaned up by the
+# `StuckImportJobsWorker`
+# - It marks the import as failed if a job failed to many times
+# - It marks the import as finished when all remaining jobs are done
+module Gitlab
+ module PhabricatorImport
+ class BaseWorker
+ include ApplicationWorker
+ include ProjectImportOptions # This marks the project as failed after too many tries
+ include Gitlab::ExclusiveLeaseHelpers
+
+ class << self
+ def schedule(project_id, *args)
+ perform_async(project_id, *args)
+ add_job(project_id)
+ end
+
+ def add_job(project_id)
+ worker_state(project_id).add_job
+ end
+
+ def remove_job(project_id)
+ worker_state(project_id).remove_job
+ end
+
+ def worker_state(project_id)
+ Gitlab::PhabricatorImport::WorkerState.new(project_id)
+ end
+ end
+
+ def perform(project_id, *args)
+ in_lock("#{self.class.name.underscore}/#{project_id}/#{args}", ttl: 2.hours, sleep_sec: 5.seconds) do
+ project = Project.find_by_id(project_id)
+ next unless project
+
+ # Bail if the import job already failed
+ next unless project.import_state&.in_progress?
+
+ project.import_state.refresh_jid_expiration
+
+ import(project, *args)
+
+ # If this is the last running job, finish the import
+ project.after_import if self.class.worker_state(project_id).running_count < 2
+
+ self.class.remove_job(project_id)
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ # Reschedule a job if there was already a running one
+ # Running them at the same time could cause a deadlock updating the same
+ # resource
+ self.class.perform_in(30.seconds, project_id, *args)
+ end
+
+ private
+
+ def import(project, *args)
+ importer_class.new(project, *args).execute
+ end
+
+ def importer_class
+ raise NotImplementedError, "Implement `#{__method__}` on #{self.class}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/cache/map.rb b/lib/gitlab/phabricator_import/cache/map.rb
new file mode 100644
index 00000000000..fa8b37b20ca
--- /dev/null
+++ b/lib/gitlab/phabricator_import/cache/map.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Cache
+ class Map
+ def initialize(project)
+ @project = project
+ end
+
+ def get_gitlab_model(phabricator_id)
+ cached_info = get(phabricator_id)
+ return unless cached_info[:classname] && cached_info[:database_id]
+
+ cached_info[:classname].constantize.find_by_id(cached_info[:database_id])
+ end
+
+ def set_gitlab_model(object, phabricator_id)
+ set(object.class, object.id, phabricator_id)
+ end
+
+ private
+
+ attr_reader :project
+
+ def set(klass_name, object_id, phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.multi do |multi|
+ multi.mapped_hmset(key,
+ { classname: klass_name, database_id: object_id })
+ multi.expire(key, timeout)
+ end
+ end
+ end
+
+ def get(phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.pipelined do |pipe|
+ # Extend the TTL when a key was
+ pipe.expire(key, timeout)
+ pipe.mapped_hmget(key, :classname, :database_id)
+ end.last
+ end
+ end
+
+ def cache_key_for_phabricator_id(phabricator_id)
+ "#{Redis::Cache::CACHE_NAMESPACE}/phabricator-import/#{project.id}/#{phabricator_id}"
+ end
+
+ def redis
+ Gitlab::Redis::Cache
+ end
+
+ def timeout
+ # Setting the timeout to the same one as we do for clearing stuck jobs
+ # this makes sure all cache is available while the import is running.
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit.rb b/lib/gitlab/phabricator_import/conduit.rb
new file mode 100644
index 00000000000..4c64d737389
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ ApiError = Class.new(Gitlab::PhabricatorImport::BaseError)
+ ResponseError = Class.new(ApiError)
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/client.rb b/lib/gitlab/phabricator_import/conduit/client.rb
new file mode 100644
index 00000000000..4469a3f5849
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/client.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Client
+ def initialize(phabricator_url, api_token)
+ @phabricator_url = phabricator_url
+ @api_token = api_token
+ end
+
+ def get(path, params: {})
+ response = Gitlab::HTTP.get(build_url(path), body: build_params(params), headers: headers)
+ Response.parse!(response)
+ rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ # Wrap all errors from the API into an API-error.
+ raise ApiError.new(e)
+ end
+
+ private
+
+ attr_reader :phabricator_url, :api_token
+
+ def headers
+ { "Accept" => 'application/json' }
+ end
+
+ def build_url(path)
+ URI.join(phabricator_url, '/api/', path).to_s
+ end
+
+ def build_params(params)
+ params = params.dup
+ params.compact!
+ params.reverse_merge!("api.token" => api_token)
+
+ CGI.unescape(params.to_query)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/maniphest.rb b/lib/gitlab/phabricator_import/conduit/maniphest.rb
new file mode 100644
index 00000000000..848b71e49e7
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/maniphest.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Maniphest
+ def initialize(phabricator_url:, api_token:)
+ @client = Client.new(phabricator_url, api_token)
+ end
+
+ def tasks(after: nil)
+ TasksResponse.new(get_tasks(after))
+ end
+
+ private
+
+ def get_tasks(after)
+ client.get('maniphest.search',
+ params: {
+ after: after,
+ attachments: { projects: 1, subscribers: 1, columns: 1 }
+ })
+ end
+
+ attr_reader :client
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/pagination.rb b/lib/gitlab/phabricator_import/conduit/pagination.rb
new file mode 100644
index 00000000000..5f54cccdbc8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/pagination.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Pagination
+ def initialize(cursor_json)
+ @cursor_json = cursor_json
+ end
+
+ def has_next_page?
+ next_page.present?
+ end
+
+ def next_page
+ cursor_json["after"]
+ end
+
+ private
+
+ attr_reader :cursor_json
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/response.rb b/lib/gitlab/phabricator_import/conduit/response.rb
new file mode 100644
index 00000000000..6053ecfbd5e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/response.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Response
+ def self.parse!(http_response)
+ unless http_response.success?
+ raise Gitlab::PhabricatorImport::Conduit::ResponseError,
+ "Phabricator responded with #{http_response.status}"
+ end
+
+ response = new(JSON.parse(http_response.body))
+
+ unless response.success?
+ raise ResponseError,
+ "Phabricator Error: #{response.error_code}: #{response.error_info}"
+ end
+
+ response
+ rescue JSON::JSONError => e
+ raise ResponseError.new(e)
+ end
+
+ def initialize(json)
+ @json = json
+ end
+
+ def success?
+ error_code.nil?
+ end
+
+ def error_code
+ json['error_code']
+ end
+
+ def error_info
+ json['error_info']
+ end
+
+ def data
+ json_result&.fetch('data')
+ end
+
+ def pagination
+ return unless cursor_info = json_result&.fetch('cursor')
+
+ @pagination ||= Pagination.new(cursor_info)
+ end
+
+ private
+
+ attr_reader :json
+
+ def json_result
+ json['result']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/tasks_response.rb b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
new file mode 100644
index 00000000000..cbcf7259fb2
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class TasksResponse
+ def initialize(conduit_response)
+ @conduit_response = conduit_response
+ end
+
+ delegate :pagination, to: :conduit_response
+
+ def tasks
+ @tasks ||= conduit_response.data.map do |task_json|
+ Gitlab::PhabricatorImport::Representation::Task.new(task_json)
+ end
+ end
+
+ private
+
+ attr_reader :conduit_response
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/import_tasks_worker.rb b/lib/gitlab/phabricator_import/import_tasks_worker.rb
new file mode 100644
index 00000000000..c36954a8d41
--- /dev/null
+++ b/lib/gitlab/phabricator_import/import_tasks_worker.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class ImportTasksWorker < BaseWorker
+ def importer_class
+ Gitlab::PhabricatorImport::Issues::Importer
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/importer.rb b/lib/gitlab/phabricator_import/importer.rb
new file mode 100644
index 00000000000..c1797f4027e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/importer.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class Importer
+ def self.async?
+ true
+ end
+
+ def self.imports_repository?
+ # This does not really import a repository, but we want to skip all
+ # repository related tasks in the `Projects::ImportService`
+ true
+ end
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ Gitlab::Import::SetAsyncJid.set_jid(project)
+ schedule_first_tasks_page
+
+ true
+ rescue => e
+ fail_import(e.message)
+
+ false
+ end
+
+ private
+
+ attr_reader :project
+
+ def schedule_first_tasks_page
+ ImportTasksWorker.schedule(project.id)
+ end
+
+ def fail_import(message)
+ project.import_state.mark_as_failed(message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/importer.rb b/lib/gitlab/phabricator_import/issues/importer.rb
new file mode 100644
index 00000000000..a58438452ff
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/importer.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class Importer
+ def initialize(project, after = nil)
+ @project, @after = project, after
+ end
+
+ def execute
+ schedule_next_batch
+
+ tasks_response.tasks.each do |task|
+ TaskImporter.new(project, task).execute
+ end
+ end
+
+ private
+
+ attr_reader :project, :after
+
+ def schedule_next_batch
+ return unless tasks_response.pagination.has_next_page?
+
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ .schedule(project.id, tasks_response.pagination.next_page)
+ end
+
+ def tasks_response
+ @tasks_response ||= client.tasks(after: after)
+ end
+
+ def client
+ @client ||=
+ Gitlab::PhabricatorImport::Conduit::Maniphest
+ .new(phabricator_url: project.import_data.data['phabricator_url'],
+ api_token: project.import_data.credentials[:api_token])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/task_importer.rb b/lib/gitlab/phabricator_import/issues/task_importer.rb
new file mode 100644
index 00000000000..40d4392cbc1
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/task_importer.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class TaskImporter
+ def initialize(project, task)
+ @project, @task = project, task
+ end
+
+ def execute
+ # TODO: get the user from the project namespace from the username loaded by Phab-id
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60565
+ issue.author = User.ghost
+
+ # TODO: Reformat the description with attachments, escaping accidental
+ # links and add attachments
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60603
+ issue.assign_attributes(task.issue_attributes)
+
+ save!
+
+ issue
+ end
+
+ private
+
+ attr_reader :project, :task
+
+ def save!
+ # Just avoiding an extra redis call, we've already updated the expiry
+ # when reading the id from the map
+ was_persisted = issue.persisted?
+
+ issue.save! if issue.changed?
+
+ object_map.set_gitlab_model(issue, task.phabricator_id) unless was_persisted
+ end
+
+ def issue
+ @issue ||= find_issue_by_phabricator_id(task.phabricator_id) ||
+ project.issues.new
+ end
+
+ def find_issue_by_phabricator_id(phabricator_id)
+ object_map.get_gitlab_model(phabricator_id)
+ end
+
+ def object_map
+ Gitlab::PhabricatorImport::Cache::Map.new(project)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/project_creator.rb b/lib/gitlab/phabricator_import/project_creator.rb
new file mode 100644
index 00000000000..b37a5b44980
--- /dev/null
+++ b/lib/gitlab/phabricator_import/project_creator.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class ProjectCreator
+ def initialize(current_user, params)
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ return unless import_url.present? && api_token.present?
+
+ project = Projects::CreateService.new(current_user, create_params).execute
+ return project unless project.persisted?
+
+ project.project_feature.update!(project_feature_attributes)
+
+ project
+ end
+
+ private
+
+ attr_reader :current_user, :params
+
+ def create_params
+ {
+ name: project_name,
+ path: project_path,
+ namespace_id: namespace_id,
+ import_type: 'phabricator',
+ import_url: Project::UNKNOWN_IMPORT_URL,
+ import_data: import_data
+ }
+ end
+
+ def project_name
+ params[:name]
+ end
+
+ def project_path
+ params[:path]
+ end
+
+ def namespace_id
+ params[:namespace_id] || current_user.namespace_id
+ end
+
+ def import_url
+ params[:phabricator_server_url]
+ end
+
+ def api_token
+ params[:api_token]
+ end
+
+ def project_feature_attributes
+ @project_features_attributes ||= begin
+ # everything disabled except for issues
+ ProjectFeature::FEATURES.map do |feature|
+ [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
+ end.to_h.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
+ end
+ end
+
+ def import_data
+ {
+ data: {
+ phabricator_url: import_url
+ },
+ credentials: {
+ api_token: params.fetch(:api_token)
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/representation/task.rb b/lib/gitlab/phabricator_import/representation/task.rb
new file mode 100644
index 00000000000..6aedc71b626
--- /dev/null
+++ b/lib/gitlab/phabricator_import/representation/task.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Representation
+ class Task
+ def initialize(json)
+ @json = json
+ end
+
+ def phabricator_id
+ json['phid']
+ end
+
+ def issue_attributes
+ @issue_attributes ||= {
+ title: issue_title,
+ description: issue_description,
+ state: issue_state,
+ created_at: issue_created_at,
+ closed_at: issue_closed_at
+ }
+ end
+
+ private
+
+ attr_reader :json
+
+ def issue_title
+ # The 255 limit is the validation we impose on the Issue title in
+ # Issuable
+ @issue_title ||= json['fields']['name'].truncate(255)
+ end
+
+ def issue_description
+ json['fields']['description']['raw']
+ end
+
+ def issue_state
+ issue_closed_at.present? ? :closed : :opened
+ end
+
+ def issue_created_at
+ return unless json['fields']['dateCreated']
+
+ @issue_created_at ||= cast_datetime(json['fields']['dateCreated'])
+ end
+
+ def issue_closed_at
+ return unless json['fields']['dateClosed']
+
+ @issue_closed_at ||= cast_datetime(json['fields']['dateClosed'])
+ end
+
+ def cast_datetime(value)
+ Time.at(value.to_i)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/worker_state.rb b/lib/gitlab/phabricator_import/worker_state.rb
new file mode 100644
index 00000000000..38829e34509
--- /dev/null
+++ b/lib/gitlab/phabricator_import/worker_state.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class WorkerState
+ def initialize(project_id)
+ @project_id = project_id
+ end
+
+ def add_job
+ redis.with do |r|
+ r.pipelined do |pipe|
+ pipe.incr(all_jobs_key)
+ pipe.expire(all_jobs_key, timeout)
+ end
+ end
+ end
+
+ def remove_job
+ redis.with do |r|
+ r.decr(all_jobs_key)
+ end
+ end
+
+ def running_count
+ redis.with { |r| r.get(all_jobs_key) }.to_i
+ end
+
+ private
+
+ attr_reader :project_id
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+
+ def all_jobs_key
+ @all_jobs_key ||= "phabricator-import/jobs/project-#{project_id}/job-count"
+ end
+
+ def timeout
+ # Make sure we get rid of all the information after a job is marked
+ # as failed/succeeded
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index ee3ef9dad6e..487808a7baa 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -51,9 +51,6 @@ namespace :gitlab do
end
end
- # (Re)create hooks
- Rake::Task['gitlab:shell:create_hooks'].invoke
-
Gitlab::Shell.ensure_secret_token!
end
@@ -78,15 +75,6 @@ namespace :gitlab do
end
end
end
-
- desc 'Create or repair repository hooks symlink'
- task create_hooks: :gitlab_environment do
- warn_user_is_not_gitlab
-
- puts 'Creating/Repairing hooks symlinks for all repositories'
- system(*%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
- puts 'done'.color(:green)
- end
end
def setup
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a02c5e41721..f737d75ca95 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -340,6 +340,9 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "2FADevice|Registered On"
+msgstr ""
+
msgid "3 days"
msgstr ""
@@ -382,6 +385,9 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<no name set>"
+msgstr ""
+
msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
msgstr ""
@@ -451,6 +457,9 @@ msgstr ""
msgid "API Help"
msgstr ""
+msgid "API Token"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -496,6 +505,9 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Account: %{account}"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -754,6 +766,9 @@ msgstr ""
msgid "After a successful password update you will be redirected to login screen."
msgstr ""
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
msgid "All"
msgstr ""
@@ -838,9 +853,6 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occurding while fetching folder content."
-msgstr ""
-
msgid "An error occurred creating the new branch."
msgstr ""
@@ -868,6 +880,9 @@ msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
msgid "An error occurred while fetching label colors."
msgstr ""
@@ -1099,6 +1114,9 @@ msgstr ""
msgid "Are you sure you want to cancel editing this comment?"
msgstr ""
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
msgid "Are you sure you want to delete this list?"
msgstr ""
@@ -1150,9 +1168,15 @@ msgstr ""
msgid "Are you sure? Removing this GPG key does not affect already signed commits."
msgstr ""
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
msgid "Artifacts"
msgstr ""
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
msgstr ""
@@ -1734,6 +1758,9 @@ msgstr ""
msgid "Can't find variable: ZiteReader"
msgstr ""
+msgid "Can't scan the code?"
+msgstr ""
+
msgid "Cancel"
msgstr ""
@@ -1785,6 +1812,12 @@ msgstr ""
msgid "Change title"
msgstr ""
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -2798,6 +2831,9 @@ msgstr ""
msgid "Confirmation required"
msgstr ""
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -3119,6 +3155,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current password"
+msgstr ""
+
msgid "CurrentUser|Profile"
msgstr ""
@@ -3508,6 +3547,9 @@ msgstr ""
msgid "Disable shared Runners"
msgstr ""
+msgid "Disable two-factor authentication"
+msgstr ""
+
msgid "Disabled"
msgstr ""
@@ -3589,6 +3631,9 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download codes"
+msgstr ""
+
msgid "Download export"
msgstr ""
@@ -3628,6 +3673,9 @@ msgstr ""
msgid "Edit Milestone"
msgstr ""
+msgid "Edit Password"
+msgstr ""
+
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
@@ -3808,6 +3856,9 @@ msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
msgid "Enter the issue description"
msgstr ""
@@ -5018,6 +5069,9 @@ msgstr ""
msgid "I accept the|Terms of Service and Privacy Policy"
msgstr ""
+msgid "I forgot my password"
+msgstr ""
+
msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}"
msgstr ""
@@ -5090,6 +5144,9 @@ msgstr ""
msgid "If this was a mistake you can leave the %{source_type}."
msgstr ""
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
msgstr ""
@@ -5171,6 +5228,12 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
@@ -5270,6 +5333,9 @@ msgstr ""
msgid "Install Runner on Kubernetes"
msgstr ""
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
msgid "Instance Statistics"
msgstr ""
@@ -5321,6 +5387,9 @@ msgstr ""
msgid "Invalid file."
msgstr ""
+msgid "Invalid import params"
+msgstr ""
+
msgid "Invalid input, please avoid emojis"
msgstr ""
@@ -5549,6 +5618,9 @@ msgstr ""
msgid "Key (PEM)"
msgstr ""
+msgid "Key: %{key}"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -6398,6 +6470,9 @@ msgstr ""
msgid "New milestone"
msgstr ""
+msgid "New password"
+msgstr ""
+
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr ""
@@ -6856,6 +6931,15 @@ msgstr ""
msgid "Password authentication is unavailable."
msgstr ""
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
msgid "Past due"
msgstr ""
@@ -6907,9 +6991,21 @@ msgstr ""
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
msgstr ""
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
+msgid "Pin code"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
@@ -7285,6 +7381,9 @@ msgstr ""
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
+msgid "Proceed"
+msgstr ""
+
msgid "Profile"
msgstr ""
@@ -8089,6 +8188,9 @@ msgstr ""
msgid "Recent searches"
msgstr ""
+msgid "Recovery Codes"
+msgstr ""
+
msgid "Reference:"
msgstr ""
@@ -8100,6 +8202,9 @@ msgstr[1] ""
msgid "Regenerate key"
msgstr ""
+msgid "Regenerate recovery codes"
+msgstr ""
+
msgid "Regex pattern"
msgstr ""
@@ -8109,15 +8214,24 @@ msgstr ""
msgid "Register / Sign In"
msgstr ""
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
msgid "Register U2F device"
msgstr ""
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
msgid "Register and see your runners for this group."
msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Register with two-factor app"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -8534,6 +8648,9 @@ msgstr ""
msgid "Save comment"
msgstr ""
+msgid "Save password"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
@@ -8924,6 +9041,9 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
msgid "Show all activity"
msgstr ""
@@ -10269,6 +10389,9 @@ msgstr ""
msgid "Thursday"
msgstr ""
+msgid "Time based: Yes"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -10471,6 +10594,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
msgid "To define internal users, first enable new users set to external"
msgstr ""
@@ -10684,6 +10810,15 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
msgid "Two-factor Authentication has been disabled for this user"
msgstr ""
@@ -10693,6 +10828,9 @@ msgstr ""
msgid "Type"
msgstr ""
+msgid "U2F Devices (%{length})"
+msgstr ""
+
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
@@ -10873,6 +11011,12 @@ msgstr ""
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
msgid "Use group milestones to manage issues from multiple projects in the same milestone."
msgstr ""
@@ -11179,6 +11323,9 @@ msgstr ""
msgid "We heard back from your U2F device. You have been authenticated."
msgstr ""
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
@@ -11418,7 +11565,7 @@ msgstr ""
msgid "You are attempting to update a file that has changed since you started editing it."
msgstr ""
-msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
@@ -11430,6 +11577,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
msgid "You are now impersonating %{username}"
msgstr ""
@@ -11541,6 +11691,9 @@ msgstr ""
msgid "You do not have permission to leave this %{namespaceType}."
msgstr ""
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
msgid "You don't have any active chat names."
msgstr ""
@@ -11589,6 +11742,12 @@ msgstr ""
msgid "You must have permission to create a project in a namespace before forking."
msgstr ""
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
msgid "You need permission."
msgstr ""
@@ -11664,6 +11823,9 @@ msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgstr ""
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
msgid "YouTube"
msgstr ""
@@ -11829,6 +11991,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
msgid "day"
msgid_plural "days"
msgstr[0] ""
diff --git a/qa/README.md b/qa/README.md
index f75205133e6..ef6f202464d 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -1,6 +1,6 @@
# GitLab QA - End-to-end tests for GitLab
-This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md)
+This directory contains [end-to-end tests](../../../doc/development/testing_guide/end_to_end/index.md)
for GitLab. It includes the test framework and the tests themselves.
The tests can be found in `qa/specs/features` (not to be confused with the unit
@@ -29,7 +29,7 @@ verify coupling between page objects implemented as a part of GitLab QA
and corresponding views / partials / selectors in CE / EE.
Whenever `qa:selectors` job fails in your merge request, you are supposed to
-fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
+fix [page objects](../doc/development/testing_guide/end_to_end/page_objects.md). You should also trigger end-to-end tests
using `package-and-qa` manual action, to test if everything works fine.
## How can I use it?
@@ -49,10 +49,10 @@ will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blo
### Writing tests
-- [Writing tests from scratch tutorial](docs/writing_tests_from_scratch.md)
- - [Best practices](docs/best_practices.md)
- - [Using page objects](qa/page/README.md)
- - [Guidelines](docs/guidelines.md)
+- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/quick_start_guide.md)
+ - [Best practices](../doc/development/testing_guide/best_practices.md)
+ - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
+ - [Guidelines](../doc/development/testing_guide/index.md)
### Running specific tests
diff --git a/qa/qa/fixtures/auto_devops_rack/Dockerfile b/qa/qa/fixtures/auto_devops_rack/Dockerfile
new file mode 100644
index 00000000000..1f59c23ea88
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Dockerfile
@@ -0,0 +1,9 @@
+FROM ruby:2.6.3-alpine
+ADD ./ /app/
+WORKDIR /app
+ENV RACK_ENV production
+ENV PORT 5000
+EXPOSE 5000
+
+RUN bundle install
+CMD ["bundle","exec", "rackup", "-p", "5000"]
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 58de01705d7..40a3bc85195 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -25,15 +25,12 @@ module QA
private
def create_personal_access_token
- if @is_new_session
- Runtime::Browser.visit(@address, Page::Main::Login) { do_create_personal_access_token }
- else
- do_create_personal_access_token
- end
+ Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session
+ do_create_personal_access_token
end
def do_create_personal_access_token
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::PersonalAccessToken.fabricate!.access_token
end
end
diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb
new file mode 100644
index 00000000000..85085a8e996
--- /dev/null
+++ b/spec/controllers/import/phabricator_controller_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Import::PhabricatorController do
+ let(:current_user) { create(:user) }
+
+ before do
+ sign_in current_user
+ end
+
+ describe 'GET #new' do
+ subject { get :new }
+
+ context 'when the import source is not available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: [])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(phabricator_import: false)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the import is available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(200) }
+ end
+ end
+
+ describe 'POST #create' do
+ subject(:post_create) { post :create, params: params }
+
+ context 'with valid params' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'creates a project to import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ expect { post_create }.to change { current_user.namespace.projects.reload.size }.from(0).to(1)
+
+ expect(current_user.namespace.projects.last).to be_import
+ end
+ end
+
+ context 'when an import param is missing' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: nil,
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+
+ context 'when a project param is missing' do
+ let(:params) do
+ { phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 9ef00fff3b2..490e9841492 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -841,8 +841,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
it 'erases artifacts' do
- expect(job.artifacts_file.exists?).to be_falsey
- expect(job.artifacts_metadata.exists?).to be_falsey
+ expect(job.artifacts_file.present?).to be_falsey
+ expect(job.artifacts_metadata.present?).to be_falsey
end
it 'erases trace' do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index f8c494c159e..a473136b57b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -248,17 +248,6 @@ FactoryBot.define do
runner factory: :ci_runner
end
- trait :legacy_artifacts do
- after(:create) do |build, _|
- build.update!(
- legacy_artifacts_file: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip'),
- legacy_artifacts_metadata: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
- )
- end
- end
-
trait :artifacts do
after(:create) do |build|
create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 2c76c22ba69..542fa9775cd 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -45,9 +45,12 @@ FactoryBot.define do
file_type :archive
file_format :zip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
@@ -61,9 +64,12 @@ FactoryBot.define do
file_type :metadata
file_format :gzip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5c6c1c4fd15..2adeb37c98a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -89,7 +89,7 @@ describe 'Commits' do
context 'Download artifacts' do
before do
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
end
it do
@@ -119,7 +119,7 @@ describe 'Commits' do
context "when logged as reporter" do
before do
project.add_reporter(user)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
@@ -141,7 +141,7 @@ describe 'Commits' do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index 5188dc3625f..dd8900a3698 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -27,7 +27,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
before do
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file1, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
@@ -35,7 +36,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
xit 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file2, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 28ae90bc0de..8d2b1fc7e30 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -47,7 +47,6 @@ describe 'Import/Export - project import integration test', :js do
expect(project.description).to eq("Foo Bar")
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
- expect(project_hook_exists?(project)).to be true
expect(wiki_exists?(project)).to be true
expect(project.import_state.status).to eq('finished')
end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 6ce37297a7e..b5e711997a0 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -90,7 +90,7 @@ describe 'Project Jobs Permissions' do
before do
archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip')
- job.update(legacy_artifacts_file: archive)
+ create(:ci_job_artifact, :archive, file: archive, job: job)
end
context 'when public access for jobs is disabled' do
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 908c616f2fc..54b462da87a 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -28,8 +28,8 @@ describe 'User browses a job', :js do
expect(page).to have_no_css('.artifacts')
expect(build).not_to have_trace
- expect(build.artifacts_file.exists?).to be_falsy
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
expect(page).to have_content('Job has been erased')
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 77ea613b282..d0878c4088a 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -314,7 +314,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Download artifacts", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
end
@@ -338,8 +338,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context 'Artifacts expire date', :js do
before do
- job.update(legacy_artifacts_file: artifacts_file,
- artifacts_expire_at: expire_at)
+ create(:ci_job_artifact, :archive, file: artifacts_file, expire_at: expire_at, job: job)
+ job.update!(artifacts_expire_at: expire_at)
visit project_job_path(project, job)
end
@@ -981,7 +981,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe "GET /:project/jobs/:id/download", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
click_link 'Download'
@@ -989,7 +989,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Build from other project" do
before do
- job2.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job2)
end
it do
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index f564ae34f11..be05c74efdb 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -287,10 +287,17 @@ describe 'Pages' do
:ci_build,
project: project,
pipeline: pipeline,
- ref: 'HEAD',
- legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
- legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
- )
+ ref: 'HEAD')
+ end
+
+ let!(:artifact) do
+ create(:ci_job_artifact, :archive,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build)
+ end
+
+ let!(:metadata) do
+ create(:ci_job_artifact, :metadata,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build)
end
before do
diff --git a/spec/fixtures/phabricator_responses/auth_failed.json b/spec/fixtures/phabricator_responses/auth_failed.json
new file mode 100644
index 00000000000..50e57c0ba49
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/auth_failed.json
@@ -0,0 +1 @@
+{"result":null,"error_code":"ERR-INVALID-AUTH","error_info":"API token \"api-token\" has the wrong length. API tokens should be 32 characters long."}
diff --git a/spec/fixtures/phabricator_responses/maniphest.search.json b/spec/fixtures/phabricator_responses/maniphest.search.json
new file mode 100644
index 00000000000..6a965007d0c
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/maniphest.search.json
@@ -0,0 +1,98 @@
+{
+ "result": {
+ "data": [
+ {
+ "id": 283,
+ "type": "TASK",
+ "phid": "PHID-TASK-fswfs3wkowjb6cyyxtyx",
+ "fields": {
+ "name": "Things are slow",
+ "description": {
+ "raw": "Things are slow but should be fast!"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589934592,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "dateClosed": 1374657042,
+ "spacePHID": null,
+ "dateCreated": 1374616241,
+ "dateModified": 1374657044,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ },
+ {
+ "id": 284,
+ "type": "TASK",
+ "phid": "PHID-TASK-5f73nyq5sjeh4cbmcsnb",
+ "fields": {
+ "name": "Things are broken",
+ "description": {
+ "raw": "Things are broken and should be fixed"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589803520,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "dateClosed": 1375049556,
+ "spacePHID": null,
+ "dateCreated": 1374616578,
+ "dateModified": 1375049556,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ }
+ ],
+ "maps": {},
+ "query": {
+ "queryKey": null
+ },
+ "cursor": {
+ "limit": "2",
+ "after": "284",
+ "before": null,
+ "order": null
+ }
+ },
+ "error_code": null,
+ "error_info": null
+}
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 143b28728a3..027480143bd 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -101,7 +101,7 @@ describe GitlabRoutingHelper do
it 'returns project milestone edit path when given entity parent is not a Group' do
milestone = create(:milestone, group: nil)
- expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/milestones/#{milestone.iid}/edit")
+ expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit")
end
end
end
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 805bb10bda6..d9dcb08b177 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -264,7 +264,7 @@ describe('Api', () => {
const namespace = 'some namespace';
const project = 'some project';
const labelData = { some: 'data' };
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`;
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`;
const expectedData = {
label: labelData,
};
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 93a0f29af0a..9854cf49e97 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -47,7 +47,7 @@ export const BoardsMockData = {
},
],
},
- '/test/issue-boards/milestones.json': [
+ '/test/issue-boards/-/milestones.json': [
{
id: 1,
title: 'test',
@@ -58,10 +58,10 @@ export const BoardsMockData = {
'/test/-/boards/1/lists': listObj,
},
PUT: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
DELETE: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
};
@@ -71,7 +71,7 @@ export const boardsMockInterceptor = config => {
};
export const mockBoardService = (opts = {}) => {
- const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json';
+ const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json';
const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists';
const bulkUpdatePath = opts.bulkUpdatePath || '';
const boardId = opts.boardId || '1';
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 0cfcc994234..2159e4ddf16 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -58,6 +58,7 @@ describe('noteActions', () => {
it('should render emoji link', () => {
expect(wrapper.find('.js-add-award').exists()).toBe(true);
+ expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right');
});
describe('actions dropdown', () => {
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 7644d83992f..cb4701e8edc 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -29,51 +29,6 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:storage_path) { TestEnv.repos_path }
let(:user) { build(:user) }
- describe '.create_hooks' do
- let(:repo_path) { File.join(storage_path, 'hook-test.git') }
- let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path }
- let(:existing_target) { File.join(repo_path, 'foobar') }
-
- before do
- FileUtils.rm_rf(repo_path)
- FileUtils.mkdir_p(repo_path)
- end
-
- context 'hooks is a directory' do
- let(:existing_file) { File.join(hooks_dir, 'my-file') }
-
- before do
- FileUtils.mkdir_p(hooks_dir)
- FileUtils.touch(existing_file)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) }
- end
-
- context 'hooks is a valid symlink' do
- before do
- FileUtils.mkdir_p existing_target
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
-
- context 'hooks is a broken symlink' do
- before do
- FileUtils.rm_f(existing_target)
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
- end
-
describe "Respond to" do
subject { repository }
@@ -1959,13 +1914,6 @@ describe Gitlab::Git::Repository, :seed_helper do
expect { imported_repo.fsck }.not_to raise_exception
end
- it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(valid_bundle_path)
- hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
-
- expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path)
- end
-
it 'raises an error if the bundle is an attempted malicious payload' do
expect do
imported_repo.create_from_bundle(malicious_bundle_path)
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index f5df38c9aaf..ecab64a372a 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -25,18 +25,9 @@ describe Gitlab::GithubImport::ParallelImporter do
end
it 'sets the JID in Redis' do
- expect(Gitlab::SidekiqStatus)
- .to receive(:set)
- .with("github-importer/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
- .and_call_original
+ expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project).and_call_original
importer.execute
end
-
- it 'updates the import JID of the project' do
- importer.execute
-
- expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}")
- end
end
end
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
new file mode 100644
index 00000000000..66033736e01
--- /dev/null
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
+ subject { described_class.new }
+
+ describe '#analyze?' do
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(graphql_logging: false)
+ end
+
+ it 'disables the analyzer' do
+ expect(subject.analyze?(anything)).to be_falsey
+ end
+ end
+
+ context 'feature flag enabled by default' do
+ it 'enables the analyzer' do
+ expect(subject.analyze?(anything)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb
new file mode 100644
index 00000000000..4977f98b83e
--- /dev/null
+++ b/spec/lib/gitlab/graphql_logger_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GraphqlLogger do
+ subject { described_class.new('/dev/null') }
+
+ let(:now) { Time.now }
+
+ it 'builds a logger once' do
+ expect(::Logger).to receive(:new).and_call_original
+
+ subject.info('hello world')
+ subject.error('hello again')
+ end
+
+ context 'logging a GraphQL query' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs a query from JSON' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {},
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ output = subject.format_message('INFO', now, 'test', analyzer_memo)
+
+ data = JSON.parse(output)
+ expect(data['severity']).to eq('INFO')
+ expect(data['time']).to eq(now.utc.iso8601(3))
+ expect(data['complexity']).to eq(181)
+ expect(data['variables']).to eq({})
+ expect(data['depth']).to eq(0)
+ expect(data['duration']).to eq(7)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb
new file mode 100644
index 00000000000..51397280138
--- /dev/null
+++ b/spec/lib/gitlab/import/set_async_jid_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Import::SetAsyncJid do
+ describe '.set_jid', :clean_gitlab_redis_shared_state do
+ let(:project) { create(:project, :import_scheduled) }
+
+ it 'sets the JID in Redis' do
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:set)
+ .with("async-import/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ .and_call_original
+
+ described_class.set_jid(project)
+ end
+
+ it 'updates the import JID of the project' do
+ described_class.set_jid(project)
+
+ expect(project.import_state.reload.jid).to eq("async-import/#{project.id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 8a699eb1461..e2ffb2adb9b 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,11 +34,5 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'restores the repo successfully' do
expect(restorer.restore).to be_truthy
end
-
- it 'has the webhooks' do
- restorer.restore
-
- expect(project_hook_exists?(project)).to be true
- end
end
end
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 94abf9679c4..8060b5d4448 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -14,7 +14,8 @@ describe Gitlab::ImportSources do
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
'Gitea' => 'gitea',
- 'Manifest file' => 'manifest'
+ 'Manifest file' => 'manifest',
+ 'Phabricator' => 'phabricator'
}
expect(described_class.options).to eq(expected)
@@ -35,6 +36,7 @@ describe Gitlab::ImportSources do
gitlab_project
gitea
manifest
+ phabricator
)
expect(described_class.values).to eq(expected)
@@ -53,6 +55,7 @@ describe Gitlab::ImportSources do
fogbugz
gitlab_project
gitea
+ phabricator
)
expect(described_class.importer_names).to eq(expected)
@@ -70,7 +73,8 @@ describe Gitlab::ImportSources do
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
'gitea' => Gitlab::LegacyGithubImport::Importer,
- 'manifest' => nil
+ 'manifest' => nil,
+ 'phabricator' => Gitlab::PhabricatorImport::Importer
}
import_sources.each do |name, klass|
@@ -91,7 +95,8 @@ describe Gitlab::ImportSources do
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
'gitea' => 'Gitea',
- 'manifest' => 'Manifest file'
+ 'manifest' => 'Manifest file',
+ 'phabricator' => 'Phabricator'
}
import_sources.each do |name, title|
@@ -102,7 +107,7 @@ describe Gitlab::ImportSources do
end
describe 'imports_repository? checker' do
- let(:allowed_importers) { %w[github gitlab_project bitbucket_server] }
+ let(:allowed_importers) { %w[github gitlab_project bitbucket_server phabricator] }
it 'fails if any importer other than the allowed ones implements this method' do
current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) }
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index d63a2fbee04..5454d9c1af4 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -5,14 +5,12 @@ require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Client do
include LetsEncryptHelpers
- set(:private_key) { OpenSSL::PKey::RSA.new(4096).to_pem }
let(:client) { described_class.new }
before do
stub_application_setting(
lets_encrypt_notification_email: 'myemail@test.example.com',
- lets_encrypt_terms_of_service_accepted: true,
- lets_encrypt_private_key: private_key
+ lets_encrypt_terms_of_service_accepted: true
)
end
@@ -28,6 +26,36 @@ describe ::Gitlab::LetsEncrypt::Client do
)
end
+ it 'generates and stores private key and initialize acme client with it' do
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ subject
+
+ saved_private_key = Gitlab::CurrentSettings.lets_encrypt_private_key
+
+ expect(saved_private_key).to be
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ end
+
+ context 'when private key is saved in settings' do
+ let!(:saved_private_key) do
+ key = OpenSSL::PKey::RSA.new(4096).to_pem
+ Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key)
+ key
+ end
+
+ it 'uses current value of private key' do
+ subject
+
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(saved_private_key)
+ end
+ end
+
context 'when acme integration is disabled' do
before do
stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
@@ -94,6 +122,18 @@ describe ::Gitlab::LetsEncrypt::Client do
context 'when terms of service are accepted' do
it { is_expected.to eq(true) }
+ context "when private_key isn't present and database is read only" do
+ before do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'returns false' do
+ expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ is_expected.to eq(false)
+ end
+ end
+
context 'when feature flag is disabled' do
before do
stub_feature_flags(pages_auto_ssl: false)
diff --git a/spec/lib/gitlab/phabricator_import/base_worker_spec.rb b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
new file mode 100644
index 00000000000..d46d908a3e3
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::BaseWorker do
+ let(:subclass) do
+ # Creating an anonymous class for a worker is complicated, as we generate the
+ # queue name from the class name.
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ end
+
+ describe '.schedule' do
+ let(:arguments) { %w[project_id the_next_page] }
+
+ it 'schedules the job' do
+ expect(subclass).to receive(:perform_async).with(*arguments)
+
+ subclass.schedule(*arguments)
+ end
+
+ it 'counts the scheduled job', :clean_gitlab_redis_shared_state do
+ state = Gitlab::PhabricatorImport::WorkerState.new('project_id')
+
+ allow(subclass).to receive(:remove_job) # otherwise the job is removed before we saw it
+
+ expect { subclass.schedule(*arguments) }.to change { state.running_count }.by(1)
+ end
+ end
+
+ describe '#perform' do
+ let(:project) { create(:project, :import_started, import_url: "https://a.phab.instance") }
+ let(:worker) { subclass.new }
+ let(:state) { Gitlab::PhabricatorImport::WorkerState.new(project.id) }
+
+ before do
+ allow(worker).to receive(:import)
+ end
+
+ it 'does not break for a non-existing project' do
+ expect { worker.perform('not a thing') }.not_to raise_error
+ end
+
+ it 'does not do anything when the import is not in progress' do
+ project = create(:project, :import_failed)
+
+ expect(worker).not_to receive(:import)
+
+ worker.perform(project.id)
+ end
+
+ it 'calls import for the project' do
+ expect(worker).to receive(:import).with(project, 'other_arg')
+
+ worker.perform(project.id, 'other_arg')
+ end
+
+ it 'marks the project as imported if there was only one job running' do
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_finished
+ end
+
+ it 'does not mark the job as finished when there are more scheduled jobs' do
+ 2.times { state.add_job }
+
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_in_progress
+ end
+
+ it 'decrements the job counter' do
+ expect { worker.perform(project.id) }.to change { state.running_count }.by(-1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
new file mode 100644
index 00000000000..52c7a02219f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
+ set(:project) { create(:project) }
+ let(:redis) { Gitlab::Redis::Cache }
+ subject(:map) { described_class.new(project) }
+
+ describe '#get_gitlab_model' do
+ it 'returns nil if there was nothing cached for the phabricator id' do
+ expect(map.get_gitlab_model('does not exist')).to be_nil
+ end
+
+ it 'returns the object if it was set in redis' do
+ issue = create(:issue, project: project)
+ set_in_redis('exists', issue)
+
+ expect(map.get_gitlab_model('exists')).to eq(issue)
+ end
+
+ it 'extends the TTL for the cache key' do
+ set_in_redis('extend', create(:issue, project: project)) do |redis|
+ redis.expire(cache_key('extend'), 10.seconds.to_i)
+ end
+
+ map.get_gitlab_model('extend')
+
+ ttl = redis.with { |redis| redis.ttl(cache_key('extend')) }
+
+ expect(ttl).to be > 10.seconds
+ end
+ end
+
+ describe '#set_gitlab_model' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'sets the class and id in redis with a ttl' do
+ issue = create(:issue, project: project)
+
+ map.set_gitlab_model(issue, 'it is set')
+
+ set_data, ttl = redis.with do |redis|
+ redis.pipelined do |p|
+ p.mapped_hmget(cache_key('it is set'), :classname, :database_id)
+ p.ttl(cache_key('it is set'))
+ end
+ end
+
+ expect(set_data).to eq({ classname: 'Issue', database_id: issue.id.to_s })
+ expect(ttl).to be_within(1.second).of(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ end
+ end
+
+ def set_in_redis(key, object)
+ redis.with do |redis|
+ redis.mapped_hmset(cache_key(key),
+ { classname: object.class, database_id: object.id })
+ yield(redis) if block_given?
+ end
+ end
+
+ def cache_key(phabricator_id)
+ subject.__send__(:cache_key_for_phabricator_id, phabricator_id)
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
new file mode 100644
index 00000000000..542b3cd060f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Client do
+ let(:client) do
+ described_class.new('https://see-ya-later.phabricator', 'api-token')
+ end
+
+ describe '#get' do
+ it 'performs and parses a request' do
+ params = { some: 'extra', values: %w[are passed] }
+ stub_valid_request(params)
+
+ response = client.get('test', params: params)
+
+ expect(response).to be_a(Gitlab::PhabricatorImport::Conduit::Response)
+ expect(response).to be_success
+ end
+
+ it 'wraps request errors in an `ApiError`' do
+ stub_timeout
+
+ expect { client.get('test') }.to raise_error(Gitlab::PhabricatorImport::Conduit::ApiError)
+ end
+
+ it 'raises response error' do
+ stub_error_response
+
+ expect { client.get('test') }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /has the wrong length/)
+ end
+ end
+
+ def stub_valid_request(params = {})
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).with(
+ body: CGI.unescape(params.reverse_merge('api.token' => 'api-token').to_query)
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json')
+ )
+ end
+
+ def stub_timeout
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).to_timeout
+ end
+
+ def stub_error_response
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json')
+ )
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
new file mode 100644
index 00000000000..0d7714649b9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Maniphest do
+ let(:maniphest) do
+ described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
+ end
+
+ describe '#tasks' do
+ let(:fake_client) { double('Phabricator client') }
+
+ before do
+ allow(maniphest).to receive(:client).and_return(fake_client)
+ end
+
+ it 'calls the api with the correct params' do
+ expected_params = {
+ after: '123',
+ attachments: {
+ projects: 1, subscribers: 1, columns: 1
+ }
+ }
+
+ expect(fake_client).to receive(:get).with('maniphest.search',
+ params: expected_params)
+
+ maniphest.tasks(after: '123')
+ end
+
+ it 'returns a parsed response' do
+ response = Gitlab::PhabricatorImport::Conduit::Response
+ .new(fixture_file('phabricator_responses/maniphest.search.json'))
+
+ allow(fake_client).to receive(:get).and_return(response)
+
+ expect(maniphest.tasks).to be_a(Gitlab::PhabricatorImport::Conduit::TasksResponse)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
new file mode 100644
index 00000000000..a8596968f14
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Response do
+ let(:response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
+ let(:error_response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
+
+ describe '.parse!' do
+ it 'raises a ResponseError if the http response was not successfull' do
+ fake_response = double(:http_response, success?: false, status: 401)
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /responded with 401/)
+ end
+
+ it 'raises a ResponseError if the response contained a Phabricator error' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json'))
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /ERR-INVALID-AUTH: API token/)
+ end
+
+ it 'raises a ResponseError if JSON parsing failed' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: 'This is no JSON')
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/)
+ end
+
+ it 'returns a parsed response for valid input' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json'))
+
+ expect(described_class.parse!(fake_response)).to be_a(described_class)
+ end
+ end
+
+ describe '#success?' do
+ it { expect(response).to be_success }
+ it { expect(error_response).not_to be_success }
+ end
+
+ describe '#error_code' do
+ it { expect(error_response.error_code).to eq('ERR-INVALID-AUTH') }
+ it { expect(response.error_code).to be_nil }
+ end
+
+ describe '#error_info' do
+ it 'returns the correct error info' do
+ expected_message = 'API token "api-token" has the wrong length. API tokens should be 32 characters long.'
+
+ expect(error_response.error_info).to eq(expected_message)
+ end
+
+ it { expect(response.error_info).to be_nil }
+ end
+
+ describe '#data' do
+ it { expect(error_response.data).to be_nil }
+ it { expect(response.data).to be_an(Array) }
+ end
+
+ describe '#pagination' do
+ it { expect(error_response.pagination).to be_nil }
+
+ it 'builds the pagination correctly' do
+ expect(response.pagination).to be_a(Gitlab::PhabricatorImport::Conduit::Pagination)
+ expect(response.pagination.next_page).to eq('284')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
new file mode 100644
index 00000000000..4b4c2a6276e
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
+ let(:conduit_response) do
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ end
+
+ subject(:response) { described_class.new(conduit_response) }
+
+ describe '#pagination' do
+ it 'delegates to the conduit reponse' do
+ expect(response.pagination).to eq(conduit_response.pagination)
+ end
+ end
+
+ describe '#tasks' do
+ it 'builds the correct tasks representation' do
+ tasks = response.tasks
+
+ titles = tasks.map(&:issue_attributes).map { |attrs| attrs[:title] }
+
+ expect(titles).to contain_exactly('Things are slow', 'Things are broken')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
new file mode 100644
index 00000000000..1e38ef8aaa5
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ImportTasksWorker do
+ describe '#perform' do
+ it 'calls the correct importer' do
+ project = create(:project, :import_started, import_url: "https://the.phab.ulr")
+ fake_importer = instance_double(Gitlab::PhabricatorImport::Issues::Importer)
+
+ expect(Gitlab::PhabricatorImport::Issues::Importer).to receive(:new).with(project).and_return(fake_importer)
+ expect(fake_importer).to receive(:execute)
+
+ described_class.new.perform(project.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
new file mode 100644
index 00000000000..bf14010a187
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Importer do
+ it { expect(described_class).to be_async }
+
+ it "acts like it's importing repositories" do
+ expect(described_class).to be_imports_repository
+ end
+
+ describe '#execute' do
+ let(:project) { create(:project, :import_scheduled) }
+ subject(:importer) { described_class.new(project) }
+
+ it 'sets a custom jid that will be kept up to date' do
+ expect { importer.execute }.to change { project.import_state.reload.jid }
+ end
+
+ it 'starts importing tasks' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker).to receive(:schedule).with(project.id)
+
+ importer.execute
+ end
+
+ it 'marks the import as failed when something goes wrong' do
+ allow(importer).to receive(:schedule_first_tasks_page).and_raise('Stuff is broken')
+
+ importer.execute
+
+ expect(project.import_state).to be_failed
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
new file mode 100644
index 00000000000..2412cf76f79
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::Importer do
+ set(:project) { create(:project) }
+
+ let(:response) do
+ Gitlab::PhabricatorImport::Conduit::TasksResponse.new(
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ )
+ end
+
+ subject(:importer) { described_class.new(project, nil) }
+
+ before do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest)
+
+ allow(client).to receive(:tasks).and_return(response)
+ allow(importer).to receive(:client).and_return(client)
+ end
+
+ describe '#execute' do
+ it 'imports each task in the response' do
+ response.tasks.each do |task|
+ task_importer = instance_double(Gitlab::PhabricatorImport::Issues::TaskImporter)
+
+ expect(task_importer).to receive(:execute)
+ expect(Gitlab::PhabricatorImport::Issues::TaskImporter)
+ .to receive(:new).with(project, task)
+ .and_return(task_importer)
+ end
+
+ importer.execute
+ end
+
+ it 'schedules the next batch if there is one' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .to receive(:schedule).with(project.id, response.pagination.next_page)
+
+ importer.execute
+ end
+
+ it 'does not reschedule when there is no next page' do
+ allow(response.pagination).to receive(:has_next_page?).and_return(false)
+
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .not_to receive(:schedule)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
new file mode 100644
index 00000000000..1625604e754
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::TaskImporter do
+ set(:project) { create(:project) }
+ let(:task) do
+ Gitlab::PhabricatorImport::Representation::Task.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title',
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#execute' do
+ it 'creates the issue with the expected attributes' do
+ issue = described_class.new(project, task).execute
+
+ expect(issue.project).to eq(project)
+ expect(issue).to be_persisted
+ expect(issue.author).to eq(User.ghost)
+ expect(issue.title).to eq('Title')
+ expect(issue.description).to eq('# This is markdown\n it can contain more text.')
+ expect(issue).to be_closed
+ expect(issue.created_at).to eq(Time.at(1518688921))
+ expect(issue.closed_at).to eq(Time.at(1518789995))
+ end
+
+ it 'does not recreate the issue when called multiple times' do
+ expect { described_class.new(project, task).execute }
+ .to change { project.issues.reload.size }.from(0).to(1)
+ expect { described_class.new(project, task).execute }
+ .not_to change { project.issues.reload.size }
+ end
+
+ it 'does not trigger a save when the object did not change' do
+ existing_issue = create(:issue,
+ task.issue_attributes.merge(author: User.ghost))
+ importer = described_class.new(project, task)
+ allow(importer).to receive(:issue).and_return(existing_issue)
+
+ expect(existing_issue).not_to receive(:save!)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
new file mode 100644
index 00000000000..e9455b866ac
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ProjectCreator do
+ let(:user) { create(:user) }
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+ subject(:creator) { described_class.new(user, params) }
+
+ describe '#execute' do
+ it 'creates a project correctly and schedule an import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ project = creator.execute
+
+ expect(project).to be_persisted
+ expect(project).to be_import
+ expect(project.import_type).to eq('phabricator')
+ expect(project.import_data.credentials).to match(a_hash_including(api_token: 'the-token'))
+ expect(project.import_data.data).to match(a_hash_including('phabricator_url' => 'http://phab.example.com'))
+ expect(project.import_url).to eq(Project::UNKNOWN_IMPORT_URL)
+ expect(project.namespace).to eq(user.namespace)
+ end
+
+ context 'when import params are missing' do
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: '' }
+ end
+
+ it 'returns nil' do
+ expect(creator.execute).to be_nil
+ end
+ end
+
+ context 'when import params are invalid' do
+ let(:params) do
+ { path: 'new-phab-import',
+ namespace_id: '-1',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+
+ it 'returns an unpersisted project' do
+ project = creator.execute
+
+ expect(project).not_to be_persisted
+ expect(project).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
new file mode 100644
index 00000000000..dfbd8c546eb
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Representation::Task do
+ subject(:task) do
+ described_class.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#issue_attributes' do
+ it 'contains the expected values' do
+ expected_attributes = {
+ title: 'Title'.ljust(255, '.'),
+ description: '# This is markdown\n it can contain more text.',
+ state: :closed,
+ created_at: Time.at(1518688921),
+ closed_at: Time.at(1518789995)
+ }
+
+ expect(task.issue_attributes).to eq(expected_attributes)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
new file mode 100644
index 00000000000..a44947445c9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
+ subject(:state) { described_class.new('weird-project-id') }
+ let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
+
+ describe '#add_job' do
+ it 'increments the counter for jobs' do
+ set_value(3)
+
+ expect { state.add_job }.to change { get_value }.from('3').to('4')
+ end
+ end
+
+ describe '#remove_job' do
+ it 'decrements the counter for jobs' do
+ set_value(3)
+
+ expect { state.remove_job }.to change { get_value }.from('3').to('2')
+ end
+ end
+
+ describe '#running_count' do
+ it 'reads the value' do
+ set_value(9)
+
+ expect(state.running_count).to eq(9)
+ end
+
+ it 'returns 0 when nothing was set' do
+ expect(state.running_count).to eq(0)
+ end
+ end
+
+ def set_value(value)
+ redis.with { |r| r.set(key, value) }
+ end
+
+ def get_value
+ redis.with { |r| r.get(key) }
+ end
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index e2f09de2808..bce2e754176 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -612,16 +612,6 @@ describe Gitlab::Shell do
FileUtils.rm_rf(created_path)
end
- it 'creates a repository' do
- expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy
-
- expect(File.stat(created_path).mode & 0o777).to eq(0o770)
-
- hooks_path = File.join(created_path, 'hooks')
- expect(File.lstat(hooks_path)).to be_symlink
- expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path)
- end
-
it 'returns false when the command fails' do
FileUtils.mkdir_p(File.dirname(created_path))
# This file will block the creation of the repo's .git directory. That
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 9f495a5d50b..bbcb92608d8 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::UrlBuilder do
url = described_class.build(milestone)
- expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/-/milestones/#{milestone.iid}"
end
end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index 050af587061..2f594dbf9d1 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -5,11 +5,13 @@ describe Emails::PagesDomains do
include EmailSpec::Matchers
include_context 'gitlab email notification'
- set(:project) { create(:project) }
set(:domain) { create(:pages_domain, project: project) }
- set(:user) { project.owner }
+ set(:user) { project.creator }
shared_examples 'a pages domain email' do
+ let(:test_recipient) { user }
+
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 8f348b1b053..cbbb22ad78c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -45,6 +45,10 @@ describe Notify do
context 'for a project' do
shared_examples 'an assignee email' do
+ let(:test_recipient) { assignee }
+
+ it_behaves_like 'an email sent to a user'
+
it 'is sent to the assignee as the author' do
sender = subject.header[:from].addrs.first
@@ -618,8 +622,10 @@ describe Notify do
end
describe 'project was moved' do
+ let(:test_recipient) { user }
subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -773,7 +779,7 @@ describe Notify do
invitee
end
- subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@@ -1083,8 +1089,6 @@ describe Notify do
end
context 'for a group' do
- set(:group) { create(:group) }
-
describe 'group access requested' do
let(:group) { create(:group, :public, :access_requestable) }
let(:group_member) do
diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
new file mode 100644
index 00000000000..93e7e9304b1
--- /dev/null
+++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb')
+
+describe ChangePackagesSizeDefaultsInProjectStatistics, :migration do
+ let(:project_statistics) { table(:project_statistics) }
+ let(:projects) { table(:projects) }
+
+ it 'removes null packages_size' do
+ stats_to_migrate = 10
+
+ stats_to_migrate.times do |i|
+ p = projects.create!(name: "project #{i}", namespace_id: 1)
+ project_statistics.create!(project_id: p.id, namespace_id: p.namespace_id)
+ end
+
+ expect { migrate! }
+ .to change { ProjectStatistics.where(packages_size: nil).count }
+ .from(stats_to_migrate)
+ .to(0)
+ end
+
+ it 'defaults packages_size to 0' do
+ project = projects.create!(name: 'a new project', namespace_id: 2)
+ stat = project_statistics.create!(project_id: project.id, namespace_id: project.namespace_id)
+
+ expect(stat.packages_size).to be_nil
+
+ migrate!
+
+ stat.reload
+ expect(stat.packages_size).to eq(0)
+ end
+end
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
index f47cc0c36ef..773bf5222f0 100644
--- a/spec/migrations/generate_lets_encrypt_private_key_spec.rb
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -3,17 +3,9 @@ require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_p
describe GenerateLetsEncryptPrivateKey, :migration do
describe '#up' do
- let(:applications_settings) { table(:applications_settings) }
-
- it 'generates RSA private key and saves it in application settings' do
- application_setting = described_class::ApplicationSetting.create!
-
- described_class.new.up
- application_setting.reload
-
- expect(application_setting.lets_encrypt_private_key).to be_present
+ it 'does not fail' do
expect do
- OpenSSL::PKey::RSA.new(application_setting.lets_encrypt_private_key)
+ described_class.new.up
end.not_to raise_error
end
end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 79e21514506..bc826d91471 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -45,10 +45,6 @@ describe MigrateOldArtifacts, :migration, schema: 20170918072948 do
expect(build_with_legacy_artifacts.artifacts?).to be_falsey
end
- it "legacy artifacts are set" do
- expect(build_with_legacy_artifacts.legacy_artifacts_file_identifier).not_to be_nil
- end
-
describe '#min_id' do
subject { migration.send(:min_id) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 32eef9e0e01..89d18abee27 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -30,12 +30,6 @@ describe Ci::Build do
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
it { is_expected.to include_module(Ci::PipelineDelegator) }
- it { is_expected.to be_a(ArtifactMigratable) }
-
- it_behaves_like 'UpdateProjectStatistics' do
- subject { FactoryBot.build(:ci_build, pipeline: pipeline, artifacts_size: 23) }
- end
-
describe 'associations' do
it 'has a bidirectional relationship with projects' do
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
@@ -116,24 +110,6 @@ describe Ci::Build do
end
end
- context 'when job has a legacy archive' do
- let!(:job) { create(:ci_build, :legacy_artifacts) }
-
- it 'returns the job' do
- is_expected.to include(job)
- end
-
- context 'when ci_enable_legacy_artifacts feature flag is disabled' do
- before do
- stub_feature_flags(ci_enable_legacy_artifacts: false)
- end
-
- it 'does not return the job' do
- is_expected.not_to include(job)
- end
- end
- end
-
context 'when job has a job artifact archive' do
let!(:job) { create(:ci_build, :artifacts) }
@@ -464,51 +440,11 @@ describe Ci::Build do
end
end
end
-
- context 'when legacy artifacts are used' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- subject { build.artifacts? }
-
- context 'is expired' do
- let(:build) { create(:ci_build, :legacy_artifacts, :expired) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive does not exist' do
- let(:build) { create(:ci_build) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive exists' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_truthy }
-
- context 'when ci_enable_legacy_artifacts feature flag is disabled' do
- before do
- stub_feature_flags(ci_enable_legacy_artifacts: false)
- end
-
- it { is_expected.to be_falsy }
- end
- end
- end
end
describe '#browsable_artifacts?' do
subject { build.browsable_artifacts? }
- context 'artifacts metadata does not exist' do
- before do
- build.update(legacy_artifacts_metadata: nil)
- end
-
- it { is_expected.to be_falsy }
- end
-
context 'artifacts metadata does exists' do
let(:build) { create(:ci_build, :artifacts) }
@@ -764,12 +700,6 @@ describe Ci::Build do
it { is_expected.to be_truthy }
end
-
- context 'when build does not have job artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_falsy }
- end
end
describe '#has_old_trace?' do
@@ -1096,11 +1026,11 @@ describe Ci::Build do
describe 'erasable build' do
shared_examples 'erasable' do
it 'removes artifact file' do
- expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
end
it 'removes artifact metadata file' do
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
end
it 'removes all job_artifacts' do
@@ -1192,7 +1122,7 @@ describe Ci::Build do
let!(:build) { create(:ci_build, :success, :artifacts) }
before do
- build.remove_artifacts_metadata!
+ build.erase_erasable_artifacts!
end
describe '#erase' do
@@ -1203,76 +1133,6 @@ describe Ci::Build do
end
end
end
-
- context 'old artifacts' do
- context 'build is erasable' do
- context 'new artifacts' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
-
- describe '#erase' do
- before do
- build.erase(erased_by: erased_by)
- end
-
- context 'erased by user' do
- let!(:erased_by) { create(:user, username: 'eraser') }
-
- include_examples 'erasable'
-
- it 'records user who erased a build' do
- expect(build.erased_by).to eq erased_by
- end
- end
-
- context 'erased by system' do
- let(:erased_by) { nil }
-
- include_examples 'erasable'
-
- it 'does not set user who erased a build' do
- expect(build.erased_by).to be_nil
- end
- end
- end
-
- describe '#erasable?' do
- subject { build.erasable? }
- it { is_expected.to be_truthy }
- end
-
- describe '#erased?' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
- subject { build.erased? }
-
- context 'job has not been erased' do
- it { is_expected.to be_falsey }
- end
-
- context 'job has been erased' do
- before do
- build.erase
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'metadata and build trace are not available' do
- let!(:build) { create(:ci_build, :success, :legacy_artifacts) }
-
- before do
- build.remove_artifacts_metadata!
- end
-
- describe '#erase' do
- it 'does not raise error' do
- expect { build.erase }.not_to raise_error
- end
- end
- end
- end
- end
- end
end
describe '#erase_erasable_artifacts!' do
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index b82368318f2..0fa4e470eef 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -31,12 +31,28 @@ describe Milestone do
end
describe 'start_date' do
- it 'adds an error when start_date is greated then due_date' do
+ it 'adds an error when start_date is greater then due_date' do
milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)
expect(milestone).not_to be_valid
expect(milestone.errors[:due_date]).to include("must be greater than start date")
end
+
+ it 'adds an error when start_date is greater than 9999-12-31' do
+ milestone = build(:milestone, start_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:start_date]).to include("date must not be after 9999-12-31")
+ end
+ end
+
+ describe 'due_date' do
+ it 'adds an error when due_date is greater than 9999-12-31' do
+ milestone = build(:milestone, due_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31")
+ end
end
end
@@ -381,21 +397,6 @@ describe Milestone do
expect(milestone_ids).to be_empty
end
end
-
- context 'when there is a milestone with a date after 294276 AD', :postgresql do
- before do
- past_milestone_project_1.update!(due_date: Date.new(294277, 1, 1))
- end
-
- it 'returns the next upcoming open milestone ID for each project and group' do
- expect(milestone_ids).to contain_exactly(
- current_milestone_project_1.id,
- current_milestone_project_2.id,
- current_milestone_group_1.id,
- current_milestone_group_2.id
- )
- end
- end
end
describe '#to_reference' do
@@ -519,4 +520,20 @@ describe Milestone do
end
end
end
+
+ describe '.reference_pattern' do
+ subject { described_class.reference_pattern }
+
+ it { is_expected.to match('gitlab-org/gitlab-ce%123') }
+ it { is_expected.to match('gitlab-org/gitlab-ce%"my-milestone"') }
+ end
+
+ describe '.link_reference_pattern' do
+ subject { described_class.link_reference_pattern }
+
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/milestones/123") }
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/-/milestones/123") }
+ it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/issues/123") }
+ it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") }
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 08662231fdf..453f9761602 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1147,7 +1147,7 @@ describe Project do
allow(project).to receive(:avatar_in_git) { true }
end
- let(:avatar_path) { "/#{project.full_path}/avatar" }
+ let(:avatar_path) { "/#{project.full_path}/-/avatar" }
it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a132b85b878..f104da6ebba 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require 'mime/types'
describe API::Commits do
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
@@ -317,6 +319,96 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(201)
end
end
+
+ context 'when the API user is a guest' do
+ def last_commit_id(project, branch_name)
+ project.repository.find_branch(branch_name)&.dereferenced_target&.id
+ end
+
+ let(:public_project) { create(:project, :public, :repository) }
+ let!(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ end
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+ end
+
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'when posting to a forked project the user does not have write access' do
+ let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
end
describe 'delete' do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 9beea2e2594..b6ca9246399 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -111,4 +111,29 @@ describe 'GitlabSchema configurations' do
expect(graphql_errors).to be_nil
end
end
+
+ context 'logging' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs the query complexity and depth' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {}.to_s,
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7)
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo)
+
+ post_graphql(query, current_user: nil)
+ end
+
+ it 'logs using `format_message`' do
+ expect_any_instance_of(Gitlab::GraphqlLogger).to receive(:format_message)
+
+ post_graphql(query, current_user: nil)
+ end
+ end
end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index cca87c16f27..656d6f8b50b 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -16,6 +16,54 @@ describe 'GraphQL' do
end
end
+ context 'logging' do
+ shared_examples 'logging a graphql query' do
+ let(:expected_params) do
+ { query_string: query, variables: variables.to_s, duration: anything, depth: 1, complexity: 1 }
+ end
+
+ it 'logs a query with the expected params' do
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once
+
+ post_graphql(query, variables: variables)
+ end
+
+ it 'does not instantiate any query analyzers' do # they are static and re-used
+ expect(GraphQL::Analysis::QueryComplexity).not_to receive(:new)
+ expect(GraphQL::Analysis::QueryDepth).not_to receive(:new)
+
+ 2.times { post_graphql(query, variables: variables) }
+ end
+ end
+
+ context 'with no variables' do
+ let(:variables) { {} }
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'with variables' do
+ let(:variables) do
+ { "foo" => "bar" }
+ end
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'when there is an error in the logger' do
+ before do
+ allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:process_variables).and_raise(StandardError.new("oh noes!"))
+ end
+
+ it 'logs the exception in Sentry and continues with the request' do
+ expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times
+ expect(Gitlab::GraphqlLogger).to receive(:info)
+
+ post_graphql(query, variables: {})
+ end
+ end
+ end
+
context 'invalid variables' do
it 'returns an error' do
post_graphql(query, variables: "This is not JSON")
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 43462913497..7208cec357a 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -913,8 +913,8 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(201)
expect(job.job_artifacts.count).to eq(0)
expect(job.trace.exist?).to be_falsy
- expect(job.artifacts_file.exists?).to be_falsy
- expect(job.artifacts_metadata.exists?).to be_falsy
+ expect(job.artifacts_file.present?).to be_falsy
+ expect(job.artifacts_metadata.present?).to be_falsy
expect(job.has_job_artifacts?).to be_falsy
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 4006e697a41..3202050ac20 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -1632,8 +1632,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:metadata) { file_upload2 }
let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
- let(:stored_artifacts_file) { job.reload.artifacts_file.file }
- let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
+ let(:stored_artifacts_file) { job.reload.artifacts_file }
+ let(:stored_metadata_file) { job.reload.artifacts_metadata }
let(:stored_artifacts_size) { job.reload.artifacts_size }
let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
@@ -1654,9 +1654,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'stores artifacts and artifacts metadata' do
expect(response).to have_gitlab_http_status(201)
- expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
- expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
- expect(stored_artifacts_size).to eq(72821)
+ expect(stored_artifacts_file.filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.filename).to eq(metadata.original_filename)
+ expect(stored_artifacts_size).to eq(artifacts.size)
expect(stored_artifacts_sha256).to eq(artifacts_sha256)
expect(stored_metadata_sha256).to eq(metadata_sha256)
end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 106f92082e4..3fdede7914d 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -174,3 +174,15 @@ describe Import::GitlabProjectsController, 'routing' do
expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new')
end
end
+
+# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new
+# import_phabricator POST /import/phabricator(.:format) import/phabricator#create
+describe Import::PhabricatorController, 'routing' do
+ it 'to #create' do
+ expect(post("/import/phabricator")).to route_to("import/phabricator#create")
+ end
+
+ it 'to #new' do
+ expect(get("/import/phabricator/new")).to route_to("import/phabricator#new")
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 6f40e88d26f..83775b1040e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -470,18 +470,23 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:controller) { 'milestones' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
+ let(:controller_path) { '/-/milestones' }
end
it 'to #promote' do
- expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
+ expect(post('/gitlab/gitlabhq/-/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/milestones", "/gitlab/gitlabhq/-/milestones"
end
# project_labels GET /:project_id/labels(.:format) labels#index
describe Projects::LabelsController, 'routing' do
it 'to #index' do
- expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/labels", "/gitlab/gitlabhq/-/labels"
end
# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort
@@ -623,20 +628,24 @@ describe 'project routing' do
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/-/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/forks", "/gitlab/gitlabhq/-/forks"
end
# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy
describe Projects::AvatarsController, 'routing' do
it 'to #destroy' do
- expect(delete('/gitlab/gitlabhq/avatar')).to route_to(
+ expect(delete('/gitlab/gitlabhq/-/avatar')).to route_to(
'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/avatar", "/gitlab/gitlabhq/-/avatar"
end
describe Projects::PagesDomainsController, 'routing' do
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index df2376384ca..e9a26400723 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -23,7 +23,7 @@ describe Ci::RetryBuildService do
REJECT_ACCESSORS =
%i[id status user token token_encrypted coverage trace runner
- artifacts_expire_at artifacts_file artifacts_metadata artifacts_size
+ artifacts_expire_at
created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
@@ -38,7 +38,8 @@ describe Ci::RetryBuildService do
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason
sourced_pipelines artifacts_file_store artifacts_metadata_store
- metadata runner_session trace_chunks].freeze
+ metadata runner_session trace_chunks
+ artifacts_file artifacts_metadata artifacts_size].freeze
shared_examples 'build duplication' do
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 7c91f0bbe6e..b597717c347 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -27,59 +27,6 @@ describe Projects::UpdatePagesService do
it { is_expected.not_to match(Gitlab::PathRegex.namespace_format_regex) }
end
- context 'legacy artifacts' do
- before do
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metadata)
- end
-
- describe 'pages artifacts' do
- it "doesn't delete artifacts after deploying" do
- expect(execute).to eq(:success)
-
- expect(build.reload.artifacts?).to eq(true)
- end
- end
-
- it 'succeeds' do
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
-
- # Check that all expected files are extracted
- %w[index.html zero .hidden/file].each do |filename|
- expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
- end
- end
-
- it 'limits pages size' do
- stub_application_setting(max_pages_size: 1)
- expect(execute).not_to eq(:success)
- end
-
- it 'removes pages after destroy' do
- expect(PagesWorker).to receive(:perform_in)
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
- project.destroy
- expect(project.pages_deployed?).to be_falsey
- end
-
- it 'fails if sha on branch is not latest' do
- build.update(ref: 'feature')
-
- expect(execute).not_to eq(:success)
- end
-
- it 'fails for empty file fails' do
- build.update(legacy_artifacts_file: empty_file)
-
- expect { execute }
- .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
- end
- end
-
context 'for new artifacts' do
context "for a valid job" do
before do
@@ -207,7 +154,7 @@ describe Projects::UpdatePagesService do
end
it 'fails for invalid archive' do
- build.update(legacy_artifacts_file: invalid_file)
+ create(:ci_job_artifact, :archive, file: invalid_file, job: build)
expect(execute).not_to eq(:success)
end
@@ -218,8 +165,8 @@ describe Projects::UpdatePagesService do
file = fixture_file_upload('spec/fixtures/pages.zip')
metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metafile)
+ create(:ci_job_artifact, :archive, file: file, job: build)
+ create(:ci_job_artifact, :metadata, file: metafile, job: build)
allow(build).to receive(:artifacts_metadata_entry)
.and_return(metadata)
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
index 99a7c39852e..99c5871ba54 100644
--- a/spec/support/helpers/git_helpers.rb
+++ b/spec/support/helpers/git_helpers.rb
@@ -6,12 +6,4 @@ module GitHelpers
Rugged::Repository.new(path)
end
-
- def project_hook_exists?(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project_path = project.repository.raw_repository.path
-
- File.exist?(File.join(project_path, 'hooks', 'post-receive'))
- end
- end
end
diff --git a/spec/support/matchers/eq_pem.rb b/spec/support/matchers/eq_pem.rb
new file mode 100644
index 00000000000..158281e4a19
--- /dev/null
+++ b/spec/support/matchers/eq_pem.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :eq_pem do |expected_pem_string|
+ match do |actual|
+ actual.to_pem == expected_pem_string
+ end
+
+ description do
+ "contain pem #{expected_pem_string}"
+ end
+end
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 4fff1c4e228..897c9106d77 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,5 +1,7 @@
shared_context 'gitlab email notification' do
- set(:project) { create(:project, :repository, name: 'a-known-name') }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project) { create(:project, :repository, name: 'a-known-name', group: group) }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
@@ -39,6 +41,47 @@ shared_examples 'an email sent from GitLab' do
end
end
+shared_examples 'an email sent to a user' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ it 'is sent to user\'s global notification email address' do
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+
+ context 'that is part of a project\'s group' do
+ it 'is sent to user\'s group notification email address when set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email)
+ expect(subject).to deliver_to(group_notification_email)
+ end
+
+ it 'is sent to user\'s global notification email address when no group email set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+
+ context 'when project is in a sub-group', :nested_groups do
+ before do
+ project.update!(group: subgroup)
+ end
+
+ it 'is sent to user\'s subgroup notification email address when set' do
+ # Set top-level group notification email address to make sure it doesn't get selected
+ create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email)
+
+ subgroup_notification_email = 'user+subgroup@example.com'
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email)
+
+ expect(subject).to deliver_to(subgroup_notification_email)
+ end
+
+ it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+end
+
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
index 8544fb62b5a..be69c10d7c8 100644
--- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -13,61 +13,6 @@ describe 'gitlab:artifacts namespace rake task' do
subject { run_rake_task('gitlab:artifacts:migrate') }
- context 'legacy artifacts' do
- describe 'migrate' do
- let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) }
-
- context 'when local storage is used' do
- let(:store) { ObjectStorage::Store::LOCAL }
-
- context 'and job does not have file store defined' do
- let(:object_storage_enabled) { true }
- let(:store) { nil }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is defined' do
- let(:object_storage_enabled) { true }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is not defined' do
- it "fails to migrate to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL)
- end
- end
- end
-
- context 'when remote storage is used' do
- let(:object_storage_enabled) { true }
-
- let(:store) { ObjectStorage::Store::REMOTE }
-
- it "file stays on remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
- end
- end
-
context 'job artifacts' do
let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) }
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index a9d14070177..c3e912b02c5 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -7,14 +7,8 @@ describe 'gitlab:shell rake tasks' do
stub_warn_user_is_not_gitlab
end
- after do
- TestEnv.sabotage_gitlab_shell_hooks
- end
-
describe 'install task' do
- it 'invokes create_hooks task' do
- expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
-
+ it 'installs and compiles gitlab-shell' do
storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
end
@@ -24,14 +18,4 @@ describe 'gitlab:shell rake tasks' do
run_rake_task('gitlab:shell:install')
end
end
-
- describe 'create_hooks task' do
- it 'calls gitlab-shell bin/create_hooks' do
- expect_any_instance_of(Object).to receive(:system)
- .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks",
- *Gitlab::TaskHelpers.repository_storage_paths_args)
-
- run_rake_task('gitlab:shell:create_hooks')
- end
- end
end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 555a58e9aa1..4188e7caccb 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -8,13 +8,13 @@ describe 'tokens rake tasks' do
end
describe 'reset_all_email task' do
- it 'invokes create_hooks task' do
+ it 'changes the incoming email token' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end
end
describe 'reset_all_feed task' do
- it 'invokes create_hooks task' do
+ it 'changes the feed token for the user' do
expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end
end
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
deleted file mode 100644
index 0589563b502..00000000000
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'rails_helper'
-
-describe LegacyArtifactUploader do
- let(:store) { described_class::Store::LOCAL }
- let(:job) { create(:ci_build, artifacts_file_store: store) }
- let(:uploader) { described_class.new(job, :legacy_artifacts_file) }
- let(:local_path) { described_class.root }
-
- subject { uploader }
-
- # TODO: move to Workhorse::UploadPath
- describe '.workhorse_upload_path' do
- subject { described_class.workhorse_upload_path }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('tmp/uploads') }
- end
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z],
- cache_dir: %r[artifacts/tmp/cache],
- work_dir: %r[artifacts/tmp/work]
-
- context 'object store is remote' do
- before do
- stub_artifacts_object_storage
- end
-
- include_context 'with storage', described_class::Store::REMOTE
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z]
- end
-
- describe '#filename' do
- # we need to use uploader, as this makes to use mounter
- # which initialises uploader.file object
- let(:uploader) { job.artifacts_file }
-
- subject { uploader.filename }
-
- it { is_expected.to be_nil }
- end
-
- context 'file is stored in valid path' do
- let(:file) do
- fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip')
- end
-
- before do
- uploader.store!(file)
- end
-
- subject { uploader.file.path }
-
- it { is_expected.to start_with("#{uploader.root}") }
- it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") }
- it { is_expected.to include("/#{job.project_id}/") }
- it { is_expected.to end_with("ci_build_artifacts.zip") }
- end
-end
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index 95813d15e52..cc8970d2ba0 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -48,40 +48,6 @@ describe ObjectStorage::BackgroundMoveWorker do
end
end
- context 'for legacy artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
- let(:uploader_class) { LegacyArtifactUploader }
- let(:subject_class) { Ci::Build }
- let(:file_field) { :artifacts_file }
- let(:subject_id) { build.id }
-
- context 'when local storage is used' do
- let(:store) { local }
-
- context 'and remote storage is defined' do
- before do
- stub_artifacts_object_storage(background_upload: true)
- end
-
- it "migrates file to remote storage" do
- perform
-
- expect(build.reload.artifacts_file_store).to eq(remote)
- end
-
- context 'for artifacts_metadata' do
- let(:file_field) { :artifacts_metadata }
-
- it 'migrates metadata to remote storage' do
- perform
-
- expect(build.reload.artifacts_metadata_store).to eq(remote)
- end
- end
- end
- end
- end
-
context 'for job artifacts' do
let(:artifact) { create(:ci_job_artifact, :archive) }
let(:uploader_class) { JobArtifactUploader }
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index bdb5a3801d9..39f676f1057 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -21,7 +21,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does remove files' do
- expect(build.reload.artifacts_file.exists?).to be_falsey
+ expect(build.reload.artifacts_file.present?).to be_falsey
end
it 'does remove the job artifact record' do
@@ -40,7 +40,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do
@@ -56,7 +56,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do