summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2018-02-07 09:21:17 +0100
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2018-02-07 09:21:17 +0100
commit2cce501f452e84d9fe8c27bd0291e4984c12bc4c (patch)
tree5b98215be26330c0db4b39e2f5f39720f0c63f2c
parentadb6dd067037a3df1951b5dacae37a06541ec286 (diff)
parent4457cf9d178dc9912fd9c16427ad81b389179d00 (diff)
downloadgitlab-ce-fix/gb/fix-redundant-pipeline-stages.tar.gz
Merge commit '4457cf9d178dc9912fd9c16427ad81b389179d00' into fix/gb/fix-redundant-pipeline-stagesfix/gb/fix-redundant-pipeline-stages
* commit '4457cf9d178dc9912fd9c16427ad81b389179d00': (76 commits) Conflicts: spec/services/ci/retry_build_service_spec.rb
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock3
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js6
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue28
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js4
-rw-r--r--app/assets/javascripts/toggle_buttons.js2
-rw-r--r--app/assets/stylesheets/framework/modal.scss5
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/labels.scss42
-rw-r--r--app/controllers/groups/labels_controller.rb2
-rw-r--r--app/controllers/projects/clusters/gcp_controller.rb2
-rw-r--r--app/controllers/projects/clusters_controller.rb6
-rw-r--r--app/controllers/projects/git_http_controller.rb15
-rw-r--r--app/controllers/projects/wikis_controller.rb4
-rw-r--r--app/helpers/groups_helper.rb4
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/job_artifact.rb5
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/concerns/artifact_migratable.rb3
-rw-r--r--app/models/concerns/routable.rb8
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/project_services/kubernetes_service.rb11
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/route.rb2
-rw-r--r--app/models/upload.rb8
-rw-r--r--app/models/user.rb46
-rw-r--r--app/services/ci/create_trace_artifact_service.rb16
-rw-r--r--app/services/clusters/create_service.rb2
-rw-r--r--app/services/clusters/gcp/verify_provision_status_service.rb2
-rw-r--r--app/services/files/create_service.rb12
-rw-r--r--app/services/lfs/file_modification_handler.rb42
-rw-r--r--app/uploaders/file_uploader.rb6
-rw-r--r--app/uploaders/job_artifact_uploader.rb6
-rw-r--r--app/validators/abstract_path_validator.rb6
-rw-r--r--app/validators/namespace_path_validator.rb4
-rw-r--r--app/validators/project_path_validator.rb4
-rw-r--r--app/validators/user_path_validator.rb15
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml7
-rw-r--r--app/views/projects/clusters/_advanced_settings.html.haml8
-rw-r--r--app/views/projects/clusters/_banner.html.haml10
-rw-r--r--app/views/projects/clusters/_cluster.html.haml4
-rw-r--r--app/views/projects/clusters/_dropdown.html.haml6
-rw-r--r--app/views/projects/clusters/_empty_state.html.haml9
-rw-r--r--app/views/projects/clusters/_integration_form.html.haml10
-rw-r--r--app/views/projects/clusters/_sidebar.html.haml6
-rw-r--r--app/views/projects/clusters/gcp/_form.html.haml8
-rw-r--r--app/views/projects/clusters/gcp/_header.html.haml6
-rw-r--r--app/views/projects/clusters/gcp/_show.html.haml4
-rw-r--r--app/views/projects/clusters/gcp/login.html.haml4
-rw-r--r--app/views/projects/clusters/gcp/new.html.haml6
-rw-r--r--app/views/projects/clusters/index.html.haml8
-rw-r--r--app/views/projects/clusters/new.html.haml10
-rw-r--r--app/views/projects/clusters/show.html.haml13
-rw-r--r--app/views/projects/clusters/user/_form.html.haml6
-rw-r--r--app/views/projects/clusters/user/_header.html.haml4
-rw-r--r--app/views/projects/clusters/user/_show.html.haml4
-rw-r--r--app/views/projects/clusters/user/new.html.haml6
-rw-r--r--app/views/shared/_delete_label_modal.html.haml20
-rw-r--r--app/views/shared/_label.html.haml37
-rw-r--r--app/views/shared/_label_row.html.haml18
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/build_finished_worker.rb8
-rw-r--r--app/workers/create_trace_artifact_worker.rb10
-rw-r--r--app/workers/project_cache_worker.rb2
-rw-r--r--changelogs/unreleased/14256-upload-destroy-removes-file.yml5
-rw-r--r--changelogs/unreleased/26388-push-to-create-a-new-project.yml5
-rw-r--r--changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml4
-rw-r--r--changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml5
-rw-r--r--changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml5
-rw-r--r--changelogs/unreleased/42730-close-rugged-repository.yml5
-rw-r--r--changelogs/unreleased/bump-workhorse.yml5
-rw-r--r--changelogs/unreleased/dm-route-path-validation.yml5
-rw-r--r--changelogs/unreleased/dm-user-namespace-route-path-validation.yml5
-rw-r--r--changelogs/unreleased/feature-sm-artifacts-trace.yml5
-rw-r--r--changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml5
-rw-r--r--changelogs/unreleased/jej-upload-file-tracks-lfs.yml5
-rw-r--r--changelogs/unreleased/winh-kubernetes-clusters.yml5
-rw-r--r--changelogs/unreleased/zj-protobuf.yml5
-rw-r--r--db/migrate/20180206200543_reset_events_primary_key_sequence.rb35
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/users.md5
-rw-r--r--doc/development/file_storage.md4
-rw-r--r--doc/gitlab-basics/create-project.md35
-rw-r--r--doc/install/kubernetes/gitlab_chart.md12
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md11
-rw-r--r--features/steps/project/commits/commits.rb6
-rw-r--r--features/steps/project/issues/labels.rb5
-rw-r--r--lib/api/helpers/internal_helpers.rb12
-rw-r--r--lib/api/internal.rb12
-rw-r--r--lib/api/users.rb19
-rw-r--r--lib/carrier_wave_string_file.rb5
-rw-r--r--lib/constraints/user_url_constrainer.rb2
-rw-r--r--lib/gitlab/checks/force_push.rb4
-rw-r--r--lib/gitlab/checks/post_push_message.rb46
-rw-r--r--lib/gitlab/checks/project_created.rb31
-rw-r--r--lib/gitlab/checks/project_moved.rb40
-rw-r--r--lib/gitlab/ci/trace.rb12
-rw-r--r--lib/gitlab/git/hook.rb22
-rw-r--r--lib/gitlab/git/lfs_changes.rb3
-rw-r--r--lib/gitlab/git/lfs_pointer_file.rb25
-rw-r--r--lib/gitlab/git/popen.rb2
-rw-r--r--lib/gitlab/git/repository.rb36
-rw-r--r--lib/gitlab/git/rev_list.rb63
-rw-r--r--lib/gitlab/git/wiki.rb20
-rw-r--r--lib/gitlab/git_access.rb107
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb38
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/path_regex.rb12
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb2
-rw-r--r--lib/gitlab/user_access.rb3
-rw-r--r--lib/gitlab/workhorse.rb12
-rw-r--r--locale/gitlab.pot195
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb10
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb31
-rw-r--r--spec/factories/ci/builds.rb10
-rw-r--r--spec/factories/ci/job_artifacts.rb9
-rw-r--r--spec/features/projects/clusters/applications_spec.rb4
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb32
-rw-r--r--spec/features/projects/clusters/user_spec.rb18
-rw-r--r--spec/features/projects/clusters_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb36
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/fixtures/trace/sample_trace1185
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js13
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js1
-rw-r--r--spec/javascripts/fixtures/jobs.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb10
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb46
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb87
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb3
-rw-r--r--spec/lib/gitlab/git/lfs_pointer_file_spec.rb37
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb57
-rw-r--r--spec/lib/gitlab/git_access_spec.rb236
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb8
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb17
-rw-r--r--spec/models/ci/build_spec.rb8
-rw-r--r--spec/models/ci/job_artifact_spec.rb3
-rw-r--r--spec/models/concerns/routable_spec.rb2
-rw-r--r--spec/models/group_spec.rb1
-rw-r--r--spec/models/namespace_spec.rb1
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/route_spec.rb6
-rw-r--r--spec/models/upload_spec.rb12
-rw-r--r--spec/models/user_spec.rb67
-rw-r--r--spec/models/wiki_page_spec.rb32
-rw-r--r--spec/requests/api/internal_spec.rb23
-rw-r--r--spec/requests/api/jobs_spec.rb31
-rw-r--r--spec/requests/api/runner_spec.rb18
-rw-r--r--spec/requests/api/users_spec.rb18
-rw-r--r--spec/requests/api/v3/builds_spec.rb8
-rw-r--r--spec/requests/git_http_spec.rb42
-rw-r--r--spec/services/ci/create_trace_artifact_service_spec.rb43
-rw-r--r--spec/services/ci/retry_build_service_spec.rb5
-rw-r--r--spec/services/files/create_service_spec.rb78
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb2
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb2
-rw-r--r--spec/services/users/update_service_spec.rb4
-rw-r--r--spec/uploaders/file_uploader_spec.rb23
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb27
-rw-r--r--spec/validators/user_path_validator_spec.rb38
-rw-r--r--spec/workers/build_finished_worker_spec.rb14
-rw-r--r--spec/workers/create_trace_artifact_worker_spec.rb29
170 files changed, 3234 insertions, 719 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bd14e8533ef..b53d377d909 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.78.0
+0.80.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index d5c0c991428..40c341bdcdb 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-3.5.1
+3.6.0
diff --git a/Gemfile b/Gemfile
index 52ced4a132b..27c745b99a2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -411,6 +411,8 @@ end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
+# Locked until https://github.com/google/protobuf/issues/4210 is closed
+gem 'google-protobuf', '= 3.5.1'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index cf9f160499d..e78c3c5f794 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -340,7 +340,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.5.1.1)
+ google-protobuf (3.5.1)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)
@@ -1066,6 +1066,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.13.6)
+ google-protobuf (= 3.5.1)
gpgme
grape (~> 1.0)
grape-entity (~> 0.6.0)
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 4dddb6eb0d6..3d6ec37e6dd 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -35,10 +35,11 @@ export default class Clusters {
clusterStatus,
clusterStatusReason,
helpPath,
+ ingressHelpPath,
} = document.querySelector('.js-edit-cluster-form').dataset;
this.store = new ClustersStore();
- this.store.setHelpPath(helpPath);
+ this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({
@@ -93,6 +94,7 @@ export default class Clusters {
props: {
applications: this.state.applications,
helpPath: this.state.helpPath,
+ ingressHelpPath: this.state.ingressHelpPath,
},
});
},
@@ -172,7 +174,7 @@ export default class Clusters {
.map(appId => newApplicationMap[appId].title);
if (appTitles.length > 0) {
- const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your cluster'), {
+ const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'), {
appList: appTitles.join(', '),
});
Flash(text, 'notice', this.successApplicationContainer);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index ff2e0768a87..f4259700370 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -18,11 +18,16 @@
required: false,
default: '',
},
+ ingressHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
generalApplicationDescription() {
return sprintf(
- _.escape(s__(`ClusterIntegration|Install applications on your cluster.
+ _.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`)),
{
helpLink: `<a href="${this.helpPath}">
@@ -34,7 +39,7 @@
},
helmTillerDescription() {
return _.escape(s__(
- `ClusterIntegration|Helm streamlines installing and managing Kubernets applications.
+ `ClusterIntegration|Helm streamlines installing and managing Kubernetes applications.
Tiller runs inside of your Kubernetes Cluster, and manages
releases of your charts.`,
));
@@ -49,7 +54,7 @@
_.escape(s__(
`ClusterIntegration|%{boldNotice} This will add some extra resources
like a load balancer, which may incur additional costs depending on
- the hosting provider Kubernetes is installed on. If you are using GKE,
+ the hosting provider your Kubernetes cluster is installed on. If you are using GKE,
you can %{pricingLink}.`,
)), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
@@ -59,13 +64,28 @@
false,
);
+ const externalIpParagraph = sprintf(
+ _.escape(s__(
+ `ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS
+ at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`,
+ )), {
+ ingressHelpLink: `<a href="${this.ingressHelpPath}">
+ ${_.escape(s__('ClusterIntegration|More information'))}
+ </a>`,
+ },
+ false,
+ );
+
return `
<p>
${descriptionParagraph}
</p>
- <p class="append-bottom-0">
+ <p>
${extraCostParagraph}
</p>
+ <p class="settings-message append-bottom-0">
+ ${externalIpParagraph}
+ </p>
`;
},
gitlabRunnerDescription() {
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index bd4a1fb37f9..49c3d184ef9 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -4,6 +4,7 @@ export default class ClusterStore {
constructor() {
this.state = {
helpPath: null,
+ ingressHelpPath: null,
status: null,
statusReason: null,
applications: {
@@ -39,8 +40,9 @@ export default class ClusterStore {
};
}
- setHelpPath(helpPath) {
+ setHelpPaths(helpPath, ingressHelpPath) {
this.state.helpPath = helpPath;
+ this.state.ingressHelpPath = ingressHelpPath;
}
updateStatus(status) {
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index 2d680d0f0dc..199b14458ed 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -8,7 +8,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
```
%button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if enabled?}",
- 'aria-label': _('Toggle Cluster') }
+ 'aria-label': _('Toggle Kubernetes Cluster') }
%input{ type: "hidden", class: 'js-project-feature-toggle-input', value: enabled? }
```
*/
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 32b9894ae04..a6b1bf9b099 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -4,6 +4,11 @@
.page-title {
margin-top: 0;
+
+ .color-label {
+ font-size: $gl-font-size;
+ padding: $gl-vert-padding $label-padding-modal;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 1cc22f5658d..0d21a9f5f77 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -558,6 +558,7 @@ $jq-ui-default-color: #777;
* Label
*/
$label-padding: 7px;
+$label-padding-modal: 10px;
$label-gray-bg: #f8fafc;
$label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1);
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index e8cd8a4905c..a72e654824e 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -58,13 +58,13 @@
@media (min-width: $screen-sm-min) {
width: 200px;
+ margin-left: $gl-padding * 2;
margin-bottom: 0;
}
.label {
overflow: hidden;
text-overflow: ellipsis;
- vertical-align: middle;
max-width: 100%;
}
}
@@ -79,26 +79,33 @@
width: 100px;
margin-left: 10px;
margin-bottom: 0;
- vertical-align: middle;
+ vertical-align: top;
}
}
.label-description {
display: block;
margin-bottom: 10px;
- margin-left: 50px;
+
+ .description-text {
+ margin-bottom: $gl-padding;
+ }
+
+ a {
+ color: $blue-600;
+ }
@media (min-width: $screen-sm-min) {
display: inline-block;
- width: 30%;
+ max-width: 50%;
margin-left: 10px;
margin-bottom: 0;
- vertical-align: middle;
+ vertical-align: top;
}
}
.label {
- padding: 8px 9px 9px;
+ padding: 8px 12px;
font-size: 14px;
}
}
@@ -116,6 +123,12 @@
}
.manage-labels-list {
+ @media(min-width: $screen-md-min) {
+ &.content-list li {
+ padding: $gl-padding 0;
+ }
+ }
+
> li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light;
cursor: move;
@@ -133,8 +146,6 @@
}
.btn-action {
- color: $gl-text-color;
-
.fa {
font-size: 18px;
vertical-align: middle;
@@ -155,10 +166,18 @@
float: right;
}
}
+
+ @media (max-width: $screen-xs-max) {
+ .dropdown-menu {
+ min-width: 100%;
+ }
+ }
}
.draggable-handler {
display: inline-block;
+ vertical-align: top;
+ margin: 5px 0;
opacity: 0;
transition: opacity .3s;
color: $gray-darkest;
@@ -188,7 +207,7 @@
.toggle-priority {
display: inline-block;
- vertical-align: middle;
+ vertical-align: top;
button {
border-color: transparent;
@@ -255,6 +274,11 @@
}
.label-subscribe-button {
+ @media(min-width: $screen-md-min) {
+ min-width: 105px;
+ margin-left: $gl-padding;
+ }
+
.label-subscribe-button-icon {
&[disabled] {
opacity: 0.5;
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index dda59262483..f3a9e591c3e 100644
--- a/app/controllers/groups/labels_controller.rb
+++ b/app/controllers/groups/labels_controller.rb
@@ -54,7 +54,7 @@ class Groups::LabelsController < Groups::ApplicationController
respond_to do |format|
format.html do
- redirect_to group_labels_path(@group), status: 302, notice: 'Label was removed'
+ redirect_to group_labels_path(@group), status: 302, notice: "#{@label.name} deleted permanently"
end
format.js
end
diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb
index 4fc515bd03e..94d33b91562 100644
--- a/app/controllers/projects/clusters/gcp_controller.rb
+++ b/app/controllers/projects/clusters/gcp_controller.rb
@@ -42,7 +42,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
when 'true'
return
when 'false'
- flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
+ flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
else
flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 1dc7f1b3a7f..142e8b6e4bc 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -41,7 +41,7 @@ class Projects::ClustersController < Projects::ApplicationController
head :no_content
end
format.html do
- flash[:notice] = "Cluster was successfully updated."
+ flash[:notice] = _('Kubernetes cluster was successfully updated.')
redirect_to project_cluster_path(project, cluster)
end
end
@@ -55,10 +55,10 @@ class Projects::ClustersController < Projects::ApplicationController
def destroy
if cluster.destroy
- flash[:notice] = "Cluster integration was successfully removed."
+ flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
redirect_to project_clusters_path(project), status: 302
else
- flash[:notice] = "Cluster integration was not removed."
+ flash[:notice] = _('Kubernetes cluster integration was not removed.')
render :show
end
end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 71ae60cb8cd..45910a9be44 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -5,6 +5,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404
+ rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
@@ -55,8 +56,15 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :not_found
end
+ def render_422(exception)
+ render plain: exception.message, status: :unprocessable_entity
+ end
+
def access
- @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path)
+ @access ||= access_klass.new(access_actor, project,
+ 'http', authentication_abilities: authentication_abilities,
+ namespace_path: params[:namespace_id], project_path: project_path,
+ redirected_path: redirected_path)
end
def access_actor
@@ -68,12 +76,17 @@ class Projects::GitHttpController < Projects::GitHttpClientController
# Use the magic string '_any' to indicate we do not know what the
# changes are. This is also what gitlab-shell does.
access.check(git_command, '_any')
+ @project ||= access.project
end
def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end
+ def project_path
+ @project_path ||= params[:project_id].sub(/\.git$/, '')
+ end
+
def log_user_activity
Users::ActivityService.new(user, 'pull').execute
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index fde96cbd35d..c4930d3d18d 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
if @page
- @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]),
+ @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
total_count: @page.count_versions)
- .page(params[:page])
+ .page(params[:page])
else
redirect_to(
project_wiki_path(@project, :home),
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 09450eaa5b7..23de3590b93 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,4 +1,8 @@
module GroupsHelper
+ def group_nav_link_paths
+ %w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
+ end
+
def can_change_group_visibility_level?(group)
can?(current_user, :change_visibility_level, group)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 78906e7a968..20534b8eed0 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -21,6 +21,7 @@ module Ci
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :job_artifacts_archive, -> { where(file_type: Ci::JobArtifact.file_types[:archive]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
+ has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
# The "environment" field for builds is a String, and is the unexpanded name
def persisted_environment
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 84fc6863567..0a599f72bc7 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -9,9 +9,12 @@ module Ci
mount_uploader :file, JobArtifactUploader
+ delegate :open, :exists?, to: :file
+
enum file_type: {
archive: 1,
- metadata: 2
+ metadata: 2,
+ trace: 3
}
def self.artifacts_size_for(project)
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 7f38dcc4a9c..7ce8befeeeb 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -180,7 +180,7 @@ module Clusters
return unless managed?
if api_url_changed? || token_changed? || ca_pem_changed?
- errors.add(:base, "cannot modify managed cluster")
+ errors.add(:base, _('Cannot modify managed Kubernetes cluster'))
return false
end
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
index 0460439e9e6..ff52ca64459 100644
--- a/app/models/concerns/artifact_migratable.rb
+++ b/app/models/concerns/artifact_migratable.rb
@@ -39,7 +39,6 @@ module ArtifactMigratable
end
def artifacts_size
- read_attribute(:artifacts_size).to_i +
- job_artifacts_archive&.size.to_i + job_artifacts_metadata&.size.to_i
+ read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
end
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 5c1cce98ad4..dfd7d94450b 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -7,11 +7,12 @@ module Routable
has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- validates_associated :route
validates :route, presence: true
scope :with_route, -> { includes(:route) }
+ after_validation :set_path_errors
+
before_validation do
if full_path_changed? || full_name_changed?
prepare_route
@@ -125,6 +126,11 @@ module Routable
private
+ def set_path_errors
+ route_path_errors = self.errors.delete(:"route.path")
+ self.errors[:path].concat(route_path_errors) if route_path_errors
+ end
+
def uncached_full_path
if route && route.path.present?
@full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 06655298950..d95489ee9f2 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -20,6 +20,9 @@ class Namespace < ActiveRecord::Base
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
+
+ # This should _not_ be `inverse_of: :namespace`, because that would also set
+ # `user.namespace` when this user creates a group with themselves as `owner`.
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
@@ -29,7 +32,6 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
presence: true,
- uniqueness: { scope: :parent_id },
length: { maximum: 255 },
namespace_name: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 12d5f28f5ea..3edb6109fb8 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base
validates :path,
presence: true,
project_path: true,
- length: { maximum: 255 },
- uniqueness: { scope: :namespace_id }
+ length: { maximum: 255 }
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
@@ -511,10 +510,13 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(full_path, self, disk_path: disk_path)
end
- def reload_repository!
+ def cleanup
+ @repository&.cleanup
@repository = nil
end
+ alias_method :reload_repository!, :cleanup
+
def container_registry_url
if Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index e42fd802b92..ad4ad7903ad 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -150,9 +150,10 @@ class KubernetesService < DeploymentService
end
def deprecation_message
- content = <<-MESSAGE.strip_heredoc
- Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new <a href=\'#{Gitlab::Routing.url_helpers.project_clusters_path(project)}'/>Clusters</a> page
- MESSAGE
+ content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % {
+ deprecated_message_content: deprecated_message_content,
+ url: Gitlab::Routing.url_helpers.project_clusters_path(project)
+ }
content.html_safe
end
@@ -248,9 +249,9 @@ class KubernetesService < DeploymentService
def deprecated_message_content
if active?
- "Your cluster information on this page is still editable, but you are advised to disable and reconfigure"
+ _("Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure")
else
- "Fields on this page are now uneditable, you can configure"
+ _("Fields on this page are now uneditable, you can configure")
end
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f1abe5c3e07..3d6f8f0c305 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -93,6 +93,10 @@ class Repository
alias_method :raw, :raw_repository
+ def cleanup
+ @raw_repository&.cleanup
+ end
+
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.expand_path(
diff --git a/app/models/route.rb b/app/models/route.rb
index 3d4b5a8b5ee..07d96c21cf1 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base
def ensure_permanent_paths
return if path.nil?
- errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists?
+ errors.add(:path, "has been taken before") if conflicting_redirect_exists?
end
def conflicting_redirect_exists?
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 2024228537a..99ad37dc892 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -12,6 +12,10 @@ class Upload < ActiveRecord::Base
before_save :calculate_checksum!, if: :foreground_checksummable?
after_commit :schedule_checksum, if: :checksummable?
+ # as the FileUploader is not mounted, the default CarrierWave ActiveRecord
+ # hooks are not executed and the file will not be deleted
+ after_destroy :delete_file!, if: -> { uploader_class <= FileUploader }
+
def self.hexdigest(path)
Digest::SHA256.file(path).hexdigest
end
@@ -49,6 +53,10 @@ class Upload < ActiveRecord::Base
private
+ def delete_file!
+ build_uploader.remove!
+ end
+
def checksummable?
checksum.nil? && local? && exist?
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 4b44e9bb7eb..05c93d3cb17 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -77,7 +77,7 @@ class User < ActiveRecord::Base
#
# Namespace for personal projects
- has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent
+ has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
has_many :keys, -> do
@@ -151,12 +151,9 @@ class User < ActiveRecord::Base
validates :projects_limit,
presence: true,
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
- validates :username,
- user_path: true,
- presence: true,
- uniqueness: { case_sensitive: false }
+ validates :username, presence: true
- validate :namespace_uniq, if: :username_changed?
+ validates :namespace, presence: true
validate :namespace_move_dir_allowed, if: :username_changed?
validate :unique_email, if: :email_changed?
@@ -171,7 +168,8 @@ class User < ActiveRecord::Base
before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
- after_save :ensure_namespace_correct
+ before_validation :ensure_namespace_correct
+ after_validation :set_username_errors
after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook
after_destroy :remove_key_cache
@@ -230,8 +228,8 @@ class User < ActiveRecord::Base
scope :active, -> { with_state(:active).non_internal }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
- scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) }
- scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) }
+ scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
+ scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
@@ -505,17 +503,6 @@ class User < ActiveRecord::Base
end
end
- def namespace_uniq
- # Return early if username already failed the first uniqueness validation
- return if errors.key?(:username) &&
- errors[:username].include?('has already been taken')
-
- existing_namespace = Namespace.by_path(username)
- if existing_namespace && existing_namespace != namespace
- errors.add(:username, 'has already been taken')
- end
- end
-
def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags?
errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
@@ -884,19 +871,18 @@ class User < ActiveRecord::Base
end
def ensure_namespace_correct
- # Ensure user has namespace
- create_namespace!(path: username, name: username) unless namespace
-
- if username_changed?
- unless namespace.update_attributes(path: username, name: username)
- namespace.errors.each do |attribute, message|
- self.errors.add(:"namespace_#{attribute}", message)
- end
- raise ActiveRecord::RecordInvalid.new(namespace)
- end
+ if namespace
+ namespace.path = namespace.name = username if username_changed?
+ else
+ build_namespace(path: username, name: username)
end
end
+ def set_username_errors
+ namespace_path_errors = self.errors.delete(:"namespace.path")
+ self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
+ end
+
def username_changed_hook
system_hook_service.execute_hooks_for(self, :rename)
end
diff --git a/app/services/ci/create_trace_artifact_service.rb b/app/services/ci/create_trace_artifact_service.rb
new file mode 100644
index 00000000000..280a2c3afa4
--- /dev/null
+++ b/app/services/ci/create_trace_artifact_service.rb
@@ -0,0 +1,16 @@
+module Ci
+ class CreateTraceArtifactService < BaseService
+ def execute(job)
+ return if job.job_artifacts_trace
+
+ job.trace.read do |stream|
+ if stream.file?
+ job.create_job_artifacts_trace!(
+ project: job.project,
+ file_type: :trace,
+ file: stream)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 0471b0f17a2..418888e3293 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -5,7 +5,7 @@ module Clusters
def execute(access_token = nil)
@access_token = access_token
- raise ArgumentError.new('Instance does not support multiple clusters') unless can_create_cluster?
+ raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?
create_cluster.tap do |cluster|
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb
index bc33756f27c..f994aacd086 100644
--- a/app/services/clusters/gcp/verify_provision_status_service.rb
+++ b/app/services/clusters/gcp/verify_provision_status_service.rb
@@ -28,7 +28,7 @@ module Clusters
if elapsed_time_from_creation(operation) < TIMEOUT
WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id)
else
- provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}")
+ provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 00a8dcf0934..46acdc5406c 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,10 +1,20 @@
module Files
class CreateService < Files::BaseService
def create_commit!
+ handler = Lfs::FileModificationHandler.new(project, @branch_name)
+
+ handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer|
+ create_transformed_commit(content_or_lfs_pointer)
+ end
+ end
+
+ private
+
+ def create_transformed_commit(content_or_lfs_pointer)
repository.create_file(
current_user,
@file_path,
- @file_content,
+ content_or_lfs_pointer,
message: @commit_message,
branch_name: @branch_name,
author_email: @author_email,
diff --git a/app/services/lfs/file_modification_handler.rb b/app/services/lfs/file_modification_handler.rb
new file mode 100644
index 00000000000..fe9091a6e5d
--- /dev/null
+++ b/app/services/lfs/file_modification_handler.rb
@@ -0,0 +1,42 @@
+module Lfs
+ class FileModificationHandler
+ attr_reader :project, :branch_name
+
+ delegate :repository, to: :project
+
+ def initialize(project, branch_name)
+ @project = project
+ @branch_name = branch_name
+ end
+
+ def new_file(file_path, file_content)
+ if project.lfs_enabled? && lfs_file?(file_path)
+ lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
+ lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
+ content = lfs_pointer_file.pointer
+
+ success = yield(content)
+
+ link_lfs_object!(lfs_object) if success
+ else
+ yield(file_content)
+ end
+ end
+
+ private
+
+ def lfs_file?(file_path)
+ repository.attributes_at(branch_name, file_path)['filter'] == 'lfs'
+ end
+
+ def create_lfs_object!(lfs_pointer_file, file_content)
+ LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
+ lfs_object.file = CarrierWaveStringFile.new(file_content)
+ end
+ end
+
+ def link_lfs_object!(lfs_object)
+ project.lfs_objects << lfs_object
+ end
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 2310e67cb2e..bde1161dfa8 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -15,6 +15,8 @@ class FileUploader < GitlabUploader
storage :file
+ after :remove, :prune_store_dir
+
def self.root
File.join(options.storage_path, 'uploads')
end
@@ -140,6 +142,10 @@ class FileUploader < GitlabUploader
end
end
+ def prune_store_dir
+ storage.delete_dir!(store_dir) # only remove when empty
+ end
+
def markdown_name
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index 0abb462ab7d..ad5385f45a4 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -13,6 +13,12 @@ class JobArtifactUploader < GitlabUploader
dynamic_segment
end
+ def open
+ raise 'Only File System is supported' unless file_storage?
+
+ File.open(path, "rb") if path
+ end
+
private
def dynamic_segment
diff --git a/app/validators/abstract_path_validator.rb b/app/validators/abstract_path_validator.rb
index adbccb65a84..e43b66cbe3a 100644
--- a/app/validators/abstract_path_validator.rb
+++ b/app/validators/abstract_path_validator.rb
@@ -13,10 +13,6 @@ class AbstractPathValidator < ActiveModel::EachValidator
raise NotImplementedError
end
- def self.full_path(record, value)
- value
- end
-
def self.valid_path?(path)
encode!(path)
"#{path}/" =~ path_regex
@@ -28,7 +24,7 @@ class AbstractPathValidator < ActiveModel::EachValidator
return
end
- full_path = self.class.full_path(record, value)
+ full_path = record.build_full_path
return unless full_path
unless self.class.valid_path?(full_path)
diff --git a/app/validators/namespace_path_validator.rb b/app/validators/namespace_path_validator.rb
index 4a0aa64ae0c..7b0ae4db5d4 100644
--- a/app/validators/namespace_path_validator.rb
+++ b/app/validators/namespace_path_validator.rb
@@ -12,8 +12,4 @@ class NamespacePathValidator < AbstractPathValidator
def self.format_error_message
Gitlab::PathRegex.namespace_format_message
end
-
- def self.full_path(record, value)
- record.build_full_path
- end
end
diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb
index 829b596ad3c..424fd77a6a3 100644
--- a/app/validators/project_path_validator.rb
+++ b/app/validators/project_path_validator.rb
@@ -12,8 +12,4 @@ class ProjectPathValidator < AbstractPathValidator
def self.format_error_message
Gitlab::PathRegex.project_path_format_message
end
-
- def self.full_path(record, value)
- record.build_full_path
- end
end
diff --git a/app/validators/user_path_validator.rb b/app/validators/user_path_validator.rb
deleted file mode 100644
index adf02901802..00000000000
--- a/app/validators/user_path_validator.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class UserPathValidator < AbstractPathValidator
- extend Gitlab::EncodingHelper
-
- def self.path_regex
- Gitlab::PathRegex.root_namespace_path_regex
- end
-
- def self.format_regex
- Gitlab::PathRegex.namespace_format_regex
- end
-
- def self.format_error_message
- Gitlab::PathRegex.namespace_format_message
- end
-end
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 96aae06a9df..09a43a2cac5 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -88,7 +88,7 @@
%strong.fly-out-top-item-name
#{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
+ = nav_link(path: group_nav_link_paths) do
= link_to edit_group_path(@group) do
.nav-icon-container
= sprite_icon('settings')
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index e20e58d27e1..2b98cb9de99 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -186,9 +186,9 @@
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do
- = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do
+ = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do
%span
- Clusters
+ = _('Kubernetes')
- if show_cluster_hint
.feature-highlight.js-feature-highlight{ disabled: true,
data: { trigger: 'manual',
@@ -206,13 +206,12 @@
%p
= _('Protip:')
= link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md')
- %span= _('uses clusters to deploy your code!')
+ %span= _('uses Kubernetes clusters to deploy your code!')
%hr
%button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!")
= sprite_icon('thumb-up')
-
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do
= link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/projects/clusters/_advanced_settings.html.haml
index 8a13713ae02..14979bee714 100644
--- a/app/views/projects/clusters/_advanced_settings.html.haml
+++ b/app/views/projects/clusters/_advanced_settings.html.haml
@@ -5,11 +5,11 @@
= s_('ClusterIntegration|Google Kubernetes Engine')
%p
- link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
+ = s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
.well.form-group
%label.text-danger
- = s_('ClusterIntegration|Remove cluster integration')
+ = s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p
- = s_("ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster.")
- = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster.")})
+ = s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
+ = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index 26ca3307a4a..f18caa3f4ac 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -1,14 +1,14 @@
-%h4= s_('ClusterIntegration|Cluster integration')
+%h4= s_('ClusterIntegration|Kubernetes cluster integration')
.settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine')
+ = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine')
%p.js-error-reason
.hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Cluster is being created on Google Kubernetes Engine...')
+ = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
.hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details')
+ = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
- %p= s_('ClusterIntegration|Control how your cluster integrates with GitLab')
+ %p= s_('ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab')
diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml
index 20ee8086f93..2d7f7c6b1fb 100644
--- a/app/views/projects/clusters/_cluster.html.haml
+++ b/app/views/projects/clusters/_cluster.html.haml
@@ -1,6 +1,6 @@
.gl-responsive-table-row
.table-section.section-30
- .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Cluster")
+ .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
.table-mobile-content
= link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
.table-section.section-30
@@ -14,7 +14,7 @@
.table-mobile-content
%button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
- "aria-label": s_("ClusterIntegration|Toggle Cluster"),
+ "aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"),
disabled: !cluster.can_toggle_cluster?,
data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
diff --git a/app/views/projects/clusters/_dropdown.html.haml b/app/views/projects/clusters/_dropdown.html.haml
index e36dd900f8d..d55a9c60b64 100644
--- a/app/views/projects/clusters/_dropdown.html.haml
+++ b/app/views/projects/clusters/_dropdown.html.haml
@@ -1,4 +1,4 @@
-%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration')
+%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration')
.dropdown.clusters-dropdown
%button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
@@ -7,6 +7,6 @@
= icon('chevron-down')
%ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width
%li
- = link_to(s_('ClusterIntegration|Create cluster on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project))
+ = link_to(s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project))
%li
- = link_to(s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project))
+ = link_to(s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project))
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index b525f4efc83..600d679b60c 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -3,10 +3,9 @@
.svg-content= image_tag 'illustrations/clusters_empty.svg'
.col-xs-12
.text-content
- %h4.text-center= s_('ClusterIntegration|Integrate cluster automation')
- - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
- %p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
+ %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation')
+ - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
.text-center
- = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
-
+ = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml
index 0af6e6e0577..d4c0cd82ce3 100644
--- a/app/views/projects/clusters/_integration_form.html.haml
+++ b/app/views/projects/clusters/_integration_form.html.haml
@@ -5,15 +5,15 @@
%p
- if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster)
- = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
+ = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab\'s connection to it.')
- else
- = s_('ClusterIntegration|Cluster integration is enabled for this project.')
+ = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project.')
- else
- = s_('ClusterIntegration|Cluster integration is disabled for this project.')
+ = s_('ClusterIntegration|Kubernetes cluster integration is disabled for this project.')
%label.append-bottom-10.js-cluster-enable-toggle-area
%button{ type: 'button',
class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if @cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}",
- "aria-label": s_("ClusterIntegration|Toggle Cluster"),
+ "aria-label": s_("ClusterIntegration|Toggle Kubernetes cluster"),
disabled: !can?(current_user, :update_cluster, @cluster) }
= field.hidden_field :enabled, { class: 'js-project-feature-toggle-input'}
%span.toggle-icon
@@ -23,7 +23,7 @@
.form-group
%h5= s_('ClusterIntegration|Environment scope')
%p
- = s_("ClusterIntegration|Choose which of your project's environments will use this cluster.")
+ = s_("ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster.")
= link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/projects/clusters/_sidebar.html.haml
index 761879db32b..73cd7c50922 100644
--- a/app/views/projects/clusters/_sidebar.html.haml
+++ b/app/views/projects/clusters/_sidebar.html.haml
@@ -1,7 +1,7 @@
%h4.prepend-top-0
- = s_('ClusterIntegration|Cluster integration')
+ = s_('ClusterIntegration|Kubernetes cluster integration')
%p
- = s_('ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
+ = s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
%p
- - link = link_to(s_('ClusterIntegration|cluster'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ - link = link_to(_('Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Learn more about %{link_to_documentation}').html_safe % { link_to_documentation: link }
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml
index e384b60d8d9..5739a57dcfe 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/projects/clusters/gcp/_form.html.haml
@@ -1,12 +1,12 @@
%p
- link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page}
+ = s_('ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration.').html_safe % { link_to_help_page: link_to_help_page}
= form_for @cluster, html: { class: 'prepend-top-20' }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster)
.form-group
- = field.label :name, s_('ClusterIntegration|Cluster name')
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
+ = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
+ = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
@@ -32,4 +32,4 @@
= provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4'
.form-group
- = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-success'
+ = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml
index bddb902115d..fa989943492 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/projects/clusters/gcp/_header.html.haml
@@ -1,5 +1,5 @@
%h4.prepend-top-20
- = s_('ClusterIntegration|Enter the details for your cluster')
+ = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p
= s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
%ul
@@ -8,7 +8,7 @@
= s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine }
%li
- link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
+ = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters').html_safe % { link_to_requirements: link_to_requirements }
%li
- link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
+ = s_('ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml
index f3122a1bf47..78cd687ef93 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/projects/clusters/gcp/_show.html.haml
@@ -1,10 +1,10 @@
.form-group
%label.append-bottom-10{ for: 'cluster-name' }
- = s_('ClusterIntegration|Cluster name')
+ = s_('ClusterIntegration|Kubernetes cluster name')
.input-group
%input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true }
%span.input-group-btn
- = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy cluster name'), class: 'btn-default')
+ = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'btn-default')
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster)
diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml
index 878ebaded88..dada51f39da 100644
--- a/app/views/projects/clusters/gcp/login.html.haml
+++ b/app/views/projects/clusters/gcp/login.html.haml
@@ -1,11 +1,11 @@
-- breadcrumb_title "Cluster"
+- breadcrumb_title 'Kubernetes'
- page_title _("Login")
.row.prepend-top-default
.col-sm-4
= render 'projects/clusters/sidebar'
.col-sm-8
- = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine')
+ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine')
= render 'header'
.row
.col-sm-8.col-sm-offset-4.signin-with-google
diff --git a/app/views/projects/clusters/gcp/new.html.haml b/app/views/projects/clusters/gcp/new.html.haml
index 8d92fb1e320..ea78d66d883 100644
--- a/app/views/projects/clusters/gcp/new.html.haml
+++ b/app/views/projects/clusters/gcp/new.html.haml
@@ -1,10 +1,10 @@
-- breadcrumb_title "Cluster"
-- page_title _("New Cluster")
+- breadcrumb_title 'Kubernetes'
+- page_title _("New Kubernetes Cluster")
.row.prepend-top-default
.col-sm-4
= render 'projects/clusters/sidebar'
.col-sm-8
- = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine')
+ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine')
= render 'header'
= render 'form'
diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml
index 74dbe859eea..17b244f4bf7 100644
--- a/app/views/projects/clusters/index.html.haml
+++ b/app/views/projects/clusters/index.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Clusters"
-- page_title "Clusters"
+- breadcrumb_title 'Kubernetes'
+- page_title "Kubernetes Clusters"
.clusters-container
- if @clusters.empty?
@@ -7,11 +7,11 @@
- else
.top-area.adjust
.nav-text
- = s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project")
+ = s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project")
.ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" }
- = s_("ClusterIntegration|Cluster")
+ = s_("ClusterIntegration|Kubernetes cluster")
.table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Environment scope")
.table-section.section-30{ role: "rowheader" }
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml
index ddd13f8ea96..ebb7d247125 100644
--- a/app/views/projects/clusters/new.html.haml
+++ b/app/views/projects/clusters/new.html.haml
@@ -1,13 +1,13 @@
-- breadcrumb_title "Cluster"
-- page_title _("Cluster")
+- breadcrumb_title 'Kubernetes'
+- page_title _("Kubernetes Cluster")
.row.prepend-top-default
.col-sm-4
= render 'sidebar'
.col-sm-8
- %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration')
+ %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration')
- %p= s_('ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab')
+ %p= s_('ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab')
= link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
%p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster')
- = link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
+ = link_to s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index 2049105dff6..a60afde06d2 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -1,7 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs "Clusters", project_clusters_path(@project)
+- add_to_breadcrumbs "Kubernetes Clusters", project_clusters_path(@project)
- breadcrumb_title @cluster.name
-- page_title _("Cluster")
+- page_title _("Kubernetes Cluster")
- expanded = Rails.env.test?
@@ -13,7 +13,8 @@
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
- help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications') } }
+ help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
+ ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } }
.js-cluster-application-notice
.flash-container
@@ -26,10 +27,10 @@
%section.settings#js-cluster-details{ class: ('expanded' if expanded) }
.settings-header
- %h4= s_('ClusterIntegration|Cluster details')
+ %h4= s_('ClusterIntegration|Kubernetes cluster details')
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
- %p= s_('ClusterIntegration|See and edit the details for your cluster')
+ %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content
- if @cluster.managed?
= render 'projects/clusters/gcp/show'
@@ -41,6 +42,6 @@
%h4= _('Advanced settings')
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
- %p= s_("ClusterIntegration|Advanced options on this cluster's integration")
+ %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
.settings-content
= render 'advanced_settings'
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml
index babfca0c567..2e92524ce8f 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/projects/clusters/user/_form.html.haml
@@ -1,8 +1,8 @@
= form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster)
.form-group
- = field.label :name, s_('ClusterIntegration|Cluster name')
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
+ = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
+ = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
@@ -25,4 +25,4 @@
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
.form-group
- = field.submit s_('ClusterIntegration|Add cluster'), class: 'btn btn-success'
+ = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/projects/clusters/user/_header.html.haml
index 06ac210a06d..04c7ce96a4b 100644
--- a/app/views/projects/clusters/user/_header.html.haml
+++ b/app/views/projects/clusters/user/_header.html.haml
@@ -1,5 +1,5 @@
%h4.prepend-top-20
- = s_('ClusterIntegration|Enter the details for your cluster')
+ = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p
- link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters').html_safe % { link_to_help_page: link_to_help_page }
+ = s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page }
diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml
index 5931e0b7f17..ebbf7e775c7 100644
--- a/app/views/projects/clusters/user/_show.html.haml
+++ b/app/views/projects/clusters/user/_show.html.haml
@@ -1,8 +1,8 @@
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
- = field.label :name, s_('ClusterIntegration|Cluster name')
- = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
+ = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
+ = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group
diff --git a/app/views/projects/clusters/user/new.html.haml b/app/views/projects/clusters/user/new.html.haml
index 68f38f83453..7fb75cd9cc7 100644
--- a/app/views/projects/clusters/user/new.html.haml
+++ b/app/views/projects/clusters/user/new.html.haml
@@ -1,11 +1,11 @@
-- breadcrumb_title "Cluster"
-- page_title _("New Cluster")
+- breadcrumb_title 'Kubernetes'
+- page_title _("New Kubernetes cluster")
.row.prepend-top-default
.col-sm-4
= render 'projects/clusters/sidebar'
.col-sm-8
- = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Add an existing cluster')
+ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Add an existing Kubernetes cluster')
= render 'header'
.prepend-top-20
= render 'form'
diff --git a/app/views/shared/_delete_label_modal.html.haml b/app/views/shared/_delete_label_modal.html.haml
new file mode 100644
index 00000000000..01effefc34d
--- /dev/null
+++ b/app/views/shared/_delete_label_modal.html.haml
@@ -0,0 +1,20 @@
+.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 }
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %button.close{ data: {dismiss: 'modal' } } &times;
+ %h3.page-title Delete #{render_colored_label(label, tooltip: false)} ?
+
+ .modal-body
+ %p
+ %strong= label.name
+ %span will be permanently deleted from #{label.is_a?(ProjectLabel)? label.project.name : label.group.name}. This cannot be undone.
+
+ .modal-footer
+ %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
+
+ = link_to 'Delete label',
+ destroy_label_path(label),
+ title: 'Delete',
+ method: :delete,
+ class: 'btn btn-remove'
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 8e88cecaf9e..c0eebdfaddd 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -5,10 +5,10 @@
- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
-%li{ id: label_css_id, data: { id: label.id } }
+%li.label-list-item{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
- .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
+ .visible-xs.visible-sm-inline-block.dropdown
%button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } }
Options
= icon('caret-down')
@@ -46,14 +46,19 @@
data: {confirm: 'Remove this label? Are you sure?'},
class: 'text-danger'
- .pull-right.hidden-xs.hidden-sm.hidden-md
- - if show_label_merge_requests_link
- = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
- view merge requests
- - if show_label_issues_link
- = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
- view open issues
-
+ .pull-right.hidden-xs.hidden-sm
+ - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
+ = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
+ %span.sr-only Promote to Group
+ = sprite_icon('level-up')
+ - if can?(current_user, :admin_label, label)
+ = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
+ %span.sr-only Edit
+ = sprite_icon('pencil')
+ %span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } }
+ = link_to "#", title: "Delete", class: 'btn btn-transparent btn-action remove-row', data: { toggle: "tooltip" } do
+ %span.sr-only Delete
+ = sprite_icon('remove')
- if current_user
.label-subscription.inline
- if can_subscribe_to_label_in_different_levels?(label)
@@ -76,14 +81,4 @@
%span= label_subscription_toggle_button_text(label, @project)
= icon('spinner spin', class: 'label-subscribe-button-loading')
- - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
- = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
- %span.sr-only Promote to Group
- = icon('level-up')
- - if can?(current_user, :admin_label, label)
- = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
- %span.sr-only Edit
- = icon('pencil-square-o')
- = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
- %span.sr-only Delete
- = icon('trash-o')
+= render 'shared/delete_label_modal', label: label
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 7f58298c60f..bd4f191502e 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,3 +1,7 @@
+- subject = local_assigns[:subject]
+- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
+- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
+
%span.label-row
- if can?(current_user, :admin_label, @project)
.draggable-handler
@@ -13,6 +17,14 @@
- if defined?(@project) && @project.group.present?
%span.label-type
= label.model_name.human.titleize
- - if label.description
- %span.label-description
- = markdown_field(label, :description)
+
+ %span.label-description
+ - if label.description.present?
+ .description-text
+ = markdown_field(label, :description)
+ .hidden-xs.hidden-sm
+ - if show_label_issues_link
+ = link_to_label(label, subject: subject) { 'Issues' }
+ - if show_label_merge_requests_link
+ &middot;
+ = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 50e876b1d19..f2c20114534 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -43,6 +43,7 @@
- pipeline_creation:run_pipeline_schedule
- pipeline_default:build_coverage
- pipeline_default:build_trace_sections
+- pipeline_default:create_trace_artifact
- pipeline_default:pipeline_metrics
- pipeline_default:pipeline_notification
- pipeline_default:update_head_pipeline_for_merge_request
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 97d80305bec..b5ed8d607b3 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -6,9 +6,13 @@ class BuildFinishedWorker
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
- BuildTraceSectionsWorker.perform_async(build.id)
+ # We execute that in sync as this access the files in order to access local file, and reduce IO
+ BuildTraceSectionsWorker.new.perform(build.id)
BuildCoverageWorker.new.perform(build.id)
- BuildHooksWorker.new.perform(build.id)
+
+ # We execute that async as this are two indepentent operations that can be executed after TraceSections and Coverage
+ BuildHooksWorker.perform_async(build.id)
+ CreateTraceArtifactWorker.perform_async(build.id)
end
end
end
diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb
new file mode 100644
index 00000000000..11cda58021e
--- /dev/null
+++ b/app/workers/create_trace_artifact_worker.rb
@@ -0,0 +1,10 @@
+class CreateTraceArtifactWorker
+ include ApplicationWorker
+ include PipelineQueue
+
+ def perform(job_id)
+ Ci::Build.preload(:project, :user).find_by(id: job_id).try do |job|
+ Ci::CreateTraceArtifactService.new(job.project, job.user).execute(job)
+ end
+ end
+end
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index f19bcbf946a..a993b4b2680 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -18,6 +18,8 @@ class ProjectCacheWorker
update_statistics(project, statistics.map(&:to_sym))
project.repository.refresh_method_caches(files.map(&:to_sym))
+
+ project.cleanup
end
def update_statistics(project, statistics = [])
diff --git a/changelogs/unreleased/14256-upload-destroy-removes-file.yml b/changelogs/unreleased/14256-upload-destroy-removes-file.yml
new file mode 100644
index 00000000000..d97188e23f1
--- /dev/null
+++ b/changelogs/unreleased/14256-upload-destroy-removes-file.yml
@@ -0,0 +1,5 @@
+---
+title: Deleting an upload will correctly clean up the filesystem.
+merge_request: 16799
+author:
+type: fixed
diff --git a/changelogs/unreleased/26388-push-to-create-a-new-project.yml b/changelogs/unreleased/26388-push-to-create-a-new-project.yml
new file mode 100644
index 00000000000..f641fcced37
--- /dev/null
+++ b/changelogs/unreleased/26388-push-to-create-a-new-project.yml
@@ -0,0 +1,5 @@
+---
+title: User can now git push to create a new project
+merge_request: 16547
+author:
+type: added
diff --git a/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml b/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml
new file mode 100644
index 00000000000..a2c81f6c995
--- /dev/null
+++ b/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Sort by Recent Sign-in in Admin Area
+merge_request: 13852
+author: Poornima M
diff --git a/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml b/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml
new file mode 100644
index 00000000000..24fcc38ee0e
--- /dev/null
+++ b/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add sorting options for /users API (admin only)
+merge_request: 16945
+author:
+type: added
diff --git a/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml b/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml
new file mode 100644
index 00000000000..aeadf8ffc4a
--- /dev/null
+++ b/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add a link to documentation on how to get external ip in the Kubernetes cluster details page
+merge_request: 16937
+author:
+type: added
diff --git a/changelogs/unreleased/42730-close-rugged-repository.yml b/changelogs/unreleased/42730-close-rugged-repository.yml
new file mode 100644
index 00000000000..a632f5030a5
--- /dev/null
+++ b/changelogs/unreleased/42730-close-rugged-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Close low level rugged repository in project cache worker
+merge_request: 16930
+author: Bastian Blank
+type: fixed
diff --git a/changelogs/unreleased/bump-workhorse.yml b/changelogs/unreleased/bump-workhorse.yml
new file mode 100644
index 00000000000..37ee402dac7
--- /dev/null
+++ b/changelogs/unreleased/bump-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade GitLab Workhorse to v3.6.0
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/dm-route-path-validation.yml b/changelogs/unreleased/dm-route-path-validation.yml
new file mode 100644
index 00000000000..df3ed1de1b9
--- /dev/null
+++ b/changelogs/unreleased/dm-route-path-validation.yml
@@ -0,0 +1,5 @@
+---
+title: Validate user, group and project paths consistently, and only once
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml
new file mode 100644
index 00000000000..36615e5b976
--- /dev/null
+++ b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml
@@ -0,0 +1,5 @@
+---
+title: Validate user namespace before saving so that errors persist on model
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/feature-sm-artifacts-trace.yml b/changelogs/unreleased/feature-sm-artifacts-trace.yml
new file mode 100644
index 00000000000..7654ce58aeb
--- /dev/null
+++ b/changelogs/unreleased/feature-sm-artifacts-trace.yml
@@ -0,0 +1,5 @@
+---
+title: Save traces as artifacts
+merge_request: 16702
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml b/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml
new file mode 100644
index 00000000000..883eecabe04
--- /dev/null
+++ b/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml
@@ -0,0 +1,5 @@
+---
+title: Override group sidebar links
+merge_request: 16942
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/jej-upload-file-tracks-lfs.yml b/changelogs/unreleased/jej-upload-file-tracks-lfs.yml
new file mode 100644
index 00000000000..a7cf6b6ba2c
--- /dev/null
+++ b/changelogs/unreleased/jej-upload-file-tracks-lfs.yml
@@ -0,0 +1,5 @@
+---
+title: File Upload UI can create LFS pointers based on .gitattributes
+merge_request: 16412
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-kubernetes-clusters.yml b/changelogs/unreleased/winh-kubernetes-clusters.yml
new file mode 100644
index 00000000000..387a719848d
--- /dev/null
+++ b/changelogs/unreleased/winh-kubernetes-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Replace "cluster" with "Kubernetes cluster"
+merge_request: 16778
+author:
+type: changed
diff --git a/changelogs/unreleased/zj-protobuf.yml b/changelogs/unreleased/zj-protobuf.yml
new file mode 100644
index 00000000000..830c2e82da9
--- /dev/null
+++ b/changelogs/unreleased/zj-protobuf.yml
@@ -0,0 +1,5 @@
+---
+title: Downgrade google-protobuf gem
+merge_request: 16941
+author:
+type: other
diff --git a/db/migrate/20180206200543_reset_events_primary_key_sequence.rb b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb
new file mode 100644
index 00000000000..eb5c4a6a1e7
--- /dev/null
+++ b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb
@@ -0,0 +1,35 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ResetEventsPrimaryKeySequence < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ class Event < ActiveRecord::Base
+ self.table_name = 'events'
+ end
+
+ def up
+ if Gitlab::Database.postgresql?
+ reset_primary_key_for_postgresql
+ else
+ reset_primary_key_for_mysql
+ end
+ end
+
+ def down
+ # No-op
+ end
+
+ def reset_primary_key_for_postgresql
+ reset_pk_sequence!(Event.table_name)
+ end
+
+ def reset_primary_key_for_mysql
+ amount = Event.pluck('COALESCE(MAX(id), 1)').first
+
+ execute "ALTER TABLE #{Event.table_name} AUTO_INCREMENT = #{amount}"
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 82ebe4369dd..41f313c904f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180204200836) do
+ActiveRecord::Schema.define(version: 20180206200543) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/api/users.md b/doc/api/users.md
index 1da6fcf297d..2082e45756a 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -51,6 +51,11 @@ GET /users?blocked=true
GET /users
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` |
+| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
+
```json
[
{
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 76354b92820..34a02bd2c3c 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -16,7 +16,7 @@ There are many places where file uploading is used, according to contexts:
- Project avatars
- Issues/MR/Notes Markdown attachments
- Issues/MR/Notes Legacy Markdown attachments
- - CI Build Artifacts
+ - CI Artifacts (archive, metadata, trace)
- LFS Objects
@@ -35,7 +35,7 @@ they are still not 100% standardized. You can see them below:
| Project avatars | yes | uploads/-/system/project/avatar/:id/:filename | `AvatarUploader` | Project |
| Issues/MR/Notes Markdown attachments | yes | uploads/:project_path_with_namespace/:random_hex/:filename | `FileUploader` | Project |
| Issues/MR/Notes Legacy Markdown attachments | no | uploads/-/system/note/attachment/:id/:filename | `AttachmentUploader` | Note |
-| CI Artifacts (CE) | yes | shared/artifacts/:year_:month/:project_id/:id | `ArtifactUploader` | Ci::Build |
+| CI Artifacts (CE) | yes | shared/artifacts/:disk_hash[0..1]/:disk_hash[2..3]/:disk_hash/:year_:month_:date/:job_id/:job_artifact_id (:disk_hash is SHA256 digest of project_id) | `JobArtifactUploader` | Ci::JobArtifact |
| LFS Objects (CE) | yes | shared/lfs-objects/:hex/:hex/:object_hash | `LfsObjectUploader` | LfsObject |
CI Artifacts and LFS Objects behave differently in CE and EE. In CE they inherit the `GitlabUploader`
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index e18711f3392..7b87039da84 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -33,5 +33,40 @@
1. Click **Create project**.
+## Push to create a new project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5.
+
+When you create a new repo locally, instead of going to GitLab to manually
+create a new project and then push the repo, you can directly push it to
+GitLab to create the new project, all without leaving your terminal. If you have access to that
+namespace, we will automatically create a new project under that GitLab namespace with its
+visibility set to private by default (you can later change it in the UI).
+
+This can be done by using either SSH or HTTP:
+
+```
+## Git push using SSH
+git push git@gitlab.example.com:namespace/nonexistent-project.git
+
+## Git push using HTTP
+git push https://gitlab.example.com/namespace/nonexistent-project.git
+```
+
+Once the push finishes successfully, a remote message will indicate
+the command to set the remote and the URL to the new project:
+
+```
+remote:
+remote: The private project namespace/nonexistent-project was created.
+remote:
+remote: To configure the remote, run:
+remote: git remote add origin https://gitlab.example.com/namespace/nonexistent-project.git
+remote:
+remote: To view the project, visit:
+remote: https://gitlab.example.com/namespace/nonexistent-project
+remote:
+```
+
[import it]: ../workflow/importing/README.md
[reserved]: ../user/reserved_names.md
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 96968c1e3ab..84eeacac3fd 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,17 +1,17 @@
# GitLab Helm Chart
-> **Note**:
-* This chart is deprecated, and is being replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). For more information on available charts, please see our [overview](index.md#chart-overview).
-* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+> **Note:**
+* This chart has been tested on Google Kubernetes Engine and Azure Container Service.
+
+**This chart is deprecated.** For small installations on Kubernetes today, we recommend the beta [`gitlab-omnibus` Helm chart](gitlab_omnibus.md).
+A new [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development with increased scalability and resilience, among other benefits. The cloud native chart will replace both the `gitlab` and `gitlab-omnibus` charts when available later this year.
-For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
+Due to the significant architectural changes, migrating will require backing up data out of this instance and restoring it into the new deployment. For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
## Introduction
The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. We **strongly recommend** the [gitlab-omnibus](gitlab_omnibus.md) chart.
-This chart is deprecated, and will be replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment.
-
This chart includes the following:
- Deployment using the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce) or [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee) container image
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 5a5f8d67ff5..9c5258c2cdf 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -1,17 +1,18 @@
# GitLab-Omnibus Helm Chart
-> **Note:**
-* This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md).
-* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues).
+> **Note:**.
+* This chart has been tested on Google Kubernetes Engine and Azure Container Service.
-This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
+**[This chart is beta](#limitations), and is the best way to install GitLab on Kubernetes today.** A new [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development with increased scalability and resilience, among other benefits. Once available, the cloud native chart will be the recommended installation method for Kubernetes, and this chart will be deprecated.
For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
+This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
+
## Introduction
This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned via [Let's Encrypt](https://letsencrypt.org/).
-This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment.
+This Helm chart is in beta, and is suited for small to medium deployments. It will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the significant architectural changes, migrating will require backing up data out of this instance and importing it into the new deployment.
The deployment includes:
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index bd3011b1cd8..959cf7d3e54 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'commit has ci status' do
@project.enable_ci
- pipeline = create :ci_pipeline, project: @project, sha: sample_commit.id
- create :ci_build, pipeline: pipeline
+ @pipeline = create(:ci_pipeline, project: @project, sha: sample_commit.id)
+ create(:ci_build, pipeline: @pipeline)
end
step 'repository contains ".gitlab-ci.yml" file' do
@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I see commit ci info' do
- expect(page).to have_content "Pipeline #1 pending"
+ expect(page).to have_content "Pipeline ##{@pipeline.id} pending"
end
step 'I search "submodules" commits' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 196e0fff63a..4df96e081f9 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do
page.within '.labels' do
- page.all('.remove-row').each do
- accept_confirm { first('.remove-row').click }
+ page.all('.label-list-item').each do
+ first('.remove-row').click
+ first(:link, 'Delete label').click
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index eb67de81a0d..cd59da6fc70 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -60,8 +60,20 @@ module API
false
end
+ def project_path
+ project&.path || project_path_match[:project_path]
+ end
+
+ def namespace_path
+ project&.namespace&.full_path || project_path_match[:namespace_path]
+ end
+
private
+ def project_path_match
+ @project_path_match ||= params[:project].match(Gitlab::PathRegex.full_project_git_path_regex) || {}
+ end
+
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project
if params[:gl_repository]
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 063f0d6599c..9285fb90cdc 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -42,11 +42,14 @@ module API
end
access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
- access_checker = access_checker_klass
- .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path)
+ access_checker = access_checker_klass.new(actor, project,
+ protocol, authentication_abilities: ssh_authentication_abilities,
+ namespace_path: namespace_path, project_path: project_path,
+ redirected_path: redirected_path)
begin
access_checker.check(params[:action], params[:changes])
+ @project ||= access_checker.project
rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e
return { status: false, message: e.message }
end
@@ -207,8 +210,11 @@ module API
# A user is not guaranteed to be returned; an orphaned write deploy
# key could be used
if user
- redirect_message = Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id)
+ redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)
+ project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id)
+
output[:redirected_message] = redirect_message if redirect_message
+ output[:project_created_message] = project_created_message if project_created_message
end
output
diff --git a/lib/api/users.rb b/lib/api/users.rb
index c7c2aa280d5..3cc12724b8a 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -18,6 +18,14 @@ module API
User.find_by(id: id) || not_found!('User')
end
+ def reorder_users(users)
+ if params[:order_by] && params[:sort]
+ users.reorder(params[:order_by] => params[:sort])
+ else
+ users
+ end
+ end
+
params :optional_attributes do
optional :skype, type: String, desc: 'The Skype username'
optional :linkedin, type: String, desc: 'The LinkedIn username'
@@ -35,6 +43,13 @@ module API
optional :avatar, type: File, desc: 'Avatar image for user'
all_or_none_of :extern_uid, :provider
end
+
+ params :sort_params do
+ optional :order_by, type: String, values: %w[id name username created_at updated_at],
+ default: 'id', desc: 'Return users ordered by a field'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return users sorted in ascending and descending order'
+ end
end
desc 'Get the list of users' do
@@ -53,16 +68,18 @@ module API
optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider
+ use :sort_params
use :pagination
end
get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin?
- params.except!(:created_after, :created_before)
+ params.except!(:created_after, :created_before, :order_by, :sort)
end
users = UsersFinder.new(current_user, params).execute
+ users = reorder_users(users)
authorized = can?(current_user, :read_users_list)
diff --git a/lib/carrier_wave_string_file.rb b/lib/carrier_wave_string_file.rb
new file mode 100644
index 00000000000..6c848902e4a
--- /dev/null
+++ b/lib/carrier_wave_string_file.rb
@@ -0,0 +1,5 @@
+class CarrierWaveStringFile < StringIO
+ def original_filename
+ ""
+ end
+end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
index b7633aa7cbb..3b3ed1c6ddb 100644
--- a/lib/constraints/user_url_constrainer.rb
+++ b/lib/constraints/user_url_constrainer.rb
@@ -2,7 +2,7 @@ class UserUrlConstrainer
def matches?(request)
full_path = request.params[:username]
- return false unless UserPathValidator.valid_path?(full_path)
+ return false unless NamespacePathValidator.valid_path?(full_path)
User.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
index dc5d285ea65..c9c3050cfc2 100644
--- a/lib/gitlab/checks/force_push.rb
+++ b/lib/gitlab/checks/force_push.rb
@@ -15,8 +15,8 @@ module Gitlab
.ancestor?(oldrev, newrev)
else
Gitlab::Git::RevList.new(
- path_to_repo: project.repository.path_to_repo,
- oldrev: oldrev, newrev: newrev).missed_ref.present?
+ project.repository.raw, oldrev: oldrev, newrev: newrev
+ ).missed_ref.present?
end
end
end
diff --git a/lib/gitlab/checks/post_push_message.rb b/lib/gitlab/checks/post_push_message.rb
new file mode 100644
index 00000000000..473c0385b34
--- /dev/null
+++ b/lib/gitlab/checks/post_push_message.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module Checks
+ class PostPushMessage
+ def initialize(project, user, protocol)
+ @project = project
+ @user = user
+ @protocol = protocol
+ end
+
+ def self.fetch_message(user_id, project_id)
+ key = message_key(user_id, project_id)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ message = redis.get(key)
+ redis.del(key)
+ message
+ end
+ end
+
+ def add_message
+ return unless user.present? && project.present?
+
+ Gitlab::Redis::SharedState.with do |redis|
+ key = self.class.message_key(user.id, project.id)
+ redis.setex(key, 5.minutes, message)
+ end
+ end
+
+ def message
+ raise NotImplementedError
+ end
+
+ protected
+
+ attr_reader :project, :user, :protocol
+
+ def self.message_key(user_id, project_id)
+ raise NotImplementedError
+ end
+
+ def url_to_repo
+ protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb
new file mode 100644
index 00000000000..cec270d6a58
--- /dev/null
+++ b/lib/gitlab/checks/project_created.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module Checks
+ class ProjectCreated < PostPushMessage
+ PROJECT_CREATED = "project_created".freeze
+
+ def message
+ <<~MESSAGE
+
+ The private project #{project.full_path} was successfully created.
+
+ To configure the remote, run:
+ git remote add origin #{url_to_repo}
+
+ To view the project, visit:
+ #{project_url}
+
+ MESSAGE
+ end
+
+ private
+
+ def self.message_key(user_id, project_id)
+ "#{PROJECT_CREATED}:#{user_id}:#{project_id}"
+ end
+
+ def project_url
+ Gitlab::Routing.url_helpers.project_url(project)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb
index dfb2f4d4054..3263790a876 100644
--- a/lib/gitlab/checks/project_moved.rb
+++ b/lib/gitlab/checks/project_moved.rb
@@ -1,38 +1,16 @@
module Gitlab
module Checks
- class ProjectMoved
+ class ProjectMoved < PostPushMessage
REDIRECT_NAMESPACE = "redirect_namespace".freeze
- def initialize(project, user, redirected_path, protocol)
- @project = project
- @user = user
+ def initialize(project, user, protocol, redirected_path)
@redirected_path = redirected_path
- @protocol = protocol
- end
-
- def self.fetch_redirect_message(user_id, project_id)
- redirect_key = redirect_message_key(user_id, project_id)
- Gitlab::Redis::SharedState.with do |redis|
- message = redis.get(redirect_key)
- redis.del(redirect_key)
- message
- end
- end
-
- def add_redirect_message
- # Don't bother with sending a redirect message for anonymous clones
- # because they never see it via the `/internal/post_receive` endpoint
- return unless user.present? && project.present?
-
- Gitlab::Redis::SharedState.with do |redis|
- key = self.class.redirect_message_key(user.id, project.id)
- redis.setex(key, 5.minutes, redirect_message)
- end
+ super(project, user, protocol)
end
- def redirect_message(rejected: false)
- <<~MESSAGE.strip_heredoc
+ def message(rejected: false)
+ <<~MESSAGE
Project '#{redirected_path}' was moved to '#{project.full_path}'.
Please update your Git remote:
@@ -47,17 +25,17 @@ module Gitlab
private
- attr_reader :project, :redirected_path, :protocol, :user
+ attr_reader :redirected_path
- def self.redirect_message_key(user_id, project_id)
+ def self.message_key(user_id, project_id)
"#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}"
end
def remote_url_message(rejected)
if rejected
- "git remote set-url origin #{url} and try again."
+ "git remote set-url origin #{url_to_repo} and try again."
else
- "git remote set-url origin #{url}"
+ "git remote set-url origin #{url_to_repo}"
end
end
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index baf55b1fa07..f2e5124c8a8 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -52,12 +52,14 @@ module Gitlab
end
def exist?
- current_path.present? || old_trace.present?
+ trace_artifact&.exists? || current_path.present? || old_trace.present?
end
def read
stream = Gitlab::Ci::Trace::Stream.new do
- if current_path
+ if trace_artifact
+ trace_artifact.open
+ elsif current_path
File.open(current_path, "rb")
elsif old_trace
StringIO.new(old_trace)
@@ -82,6 +84,8 @@ module Gitlab
end
def erase!
+ trace_artifact&.destroy
+
paths.each do |trace_path|
FileUtils.rm(trace_path, force: true)
end
@@ -137,6 +141,10 @@ module Gitlab
"#{job.id}.log"
) if job.project&.ci_id
end
+
+ def trace_artifact
+ job.job_artifacts_trace
+ end
end
end
end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index e29a1f7afa1..24f027d8da4 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -82,14 +82,20 @@ module Gitlab
end
def call_update_hook(gl_id, gl_username, oldrev, newrev, ref)
- Dir.chdir(repo_path) do
- env = {
- 'GL_ID' => gl_id,
- 'GL_USERNAME' => gl_username
- }
- stdout, stderr, status = Open3.capture3(env, path, ref, oldrev, newrev)
- [status.success?, (stderr.presence || stdout).gsub(/\R/, "<br>").html_safe]
- end
+ env = {
+ 'GL_ID' => gl_id,
+ 'GL_USERNAME' => gl_username,
+ 'PWD' => repo_path
+ }
+
+ options = {
+ chdir: repo_path
+ }
+
+ args = [ref, oldrev, newrev]
+
+ stdout, stderr, status = Open3.capture3(env, path, *args, options)
+ [status.success?, (stderr.presence || stdout).gsub(/\R/, "<br>").html_safe]
end
def retrieve_error_message(stderr, stdout)
diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb
index 732dd5d998a..48434047fce 100644
--- a/lib/gitlab/git/lfs_changes.rb
+++ b/lib/gitlab/git/lfs_changes.rb
@@ -25,8 +25,7 @@ module Gitlab
private
def rev_list
- ::Gitlab::Git::RevList.new(path_to_repo: @repository.path_to_repo,
- newrev: @newrev)
+ Gitlab::Git::RevList.new(@repository, newrev: @newrev)
end
end
end
diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb
new file mode 100644
index 00000000000..da12ed7d125
--- /dev/null
+++ b/lib/gitlab/git/lfs_pointer_file.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Git
+ class LfsPointerFile
+ def initialize(data)
+ @data = data
+ end
+
+ def pointer
+ @pointer ||= <<~FILE
+ version https://git-lfs.github.com/spec/v1
+ oid sha256:#{sha256}
+ size #{size}
+ FILE
+ end
+
+ def size
+ @size ||= @data.bytesize
+ end
+
+ def sha256
+ @sha256 ||= Digest::SHA256.hexdigest(@data)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb
index e0bd2bbe47b..c1767046ff0 100644
--- a/lib/gitlab/git/popen.rb
+++ b/lib/gitlab/git/popen.rb
@@ -25,7 +25,7 @@ module Gitlab
stdin.close
if lazy_block
- return lazy_block.call(stdout.lazy)
+ return [lazy_block.call(stdout.lazy), 0]
else
cmd_output << stdout.read
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index ab1362a3bb0..d7510061def 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -128,6 +128,10 @@ module Gitlab
raise NoRepository.new('no repository for such path')
end
+ def cleanup
+ @rugged&.close
+ end
+
def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end
@@ -1427,6 +1431,26 @@ module Gitlab
end
end
+ def rev_list(including: [], excluding: [], objects: false, &block)
+ args = ['rev-list']
+
+ args.push(*rev_list_param(including))
+
+ exclude_param = *rev_list_param(excluding)
+ if exclude_param.any?
+ args.push('--not')
+ args.push(*exclude_param)
+ end
+
+ args.push('--objects') if objects
+
+ run_git!(args, lazy_block: block)
+ end
+
+ def missed_ref(oldrev, newrev)
+ run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"])
+ end
+
private
def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
@@ -1475,7 +1499,7 @@ module Gitlab
Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
end
- def run_git(args, chdir: path, env: {}, nice: false, &block)
+ def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
@@ -1485,12 +1509,12 @@ module Gitlab
end
circuit_breaker.perform do
- popen(cmd, chdir, env, &block)
+ popen(cmd, chdir, env, lazy_block: lazy_block, &block)
end
end
- def run_git!(args, chdir: path, env: {}, nice: false, &block)
- output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block)
+ def run_git!(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
+ output, status = run_git(args, chdir: chdir, env: env, nice: nice, lazy_block: lazy_block, &block)
raise GitError, output unless status.zero?
@@ -2372,6 +2396,10 @@ module Gitlab
rescue Rugged::ReferenceError
0
end
+
+ def rev_list_param(spec)
+ spec == :all ? ['--all'] : spec
+ end
end
end
end
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index f8b2e7e0e21..38c3a55f96f 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -5,17 +5,17 @@ module Gitlab
class RevList
include Gitlab::Git::Popen
- attr_reader :oldrev, :newrev, :path_to_repo
+ attr_reader :oldrev, :newrev, :repository
- def initialize(path_to_repo:, newrev:, oldrev: nil)
+ def initialize(repository, newrev:, oldrev: nil)
@oldrev = oldrev
@newrev = newrev
- @path_to_repo = path_to_repo
+ @repository = repository
end
# This method returns an array of new commit references
def new_refs
- execute([*base_args, newrev, '--not', '--all'])
+ repository.rev_list(including: newrev, excluding: :all).split("\n")
end
# Finds newly added objects
@@ -28,66 +28,39 @@ module Gitlab
# When given a block it will yield objects as a lazy enumerator so
# the caller can limit work done instead of processing megabytes of data
def new_objects(require_path: nil, not_in: nil, &lazy_block)
- args = [*base_args, newrev, *not_in_refs(not_in), '--objects']
+ opts = {
+ including: newrev,
+ excluding: not_in.nil? ? :all : not_in,
+ require_path: require_path
+ }
- get_objects(args, require_path: require_path, &lazy_block)
+ get_objects(opts, &lazy_block)
end
def all_objects(require_path: nil, &lazy_block)
- args = [*base_args, '--all', '--objects']
-
- get_objects(args, require_path: require_path, &lazy_block)
+ get_objects(including: :all, require_path: require_path, &lazy_block)
end
# This methods returns an array of missed references
#
# Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348.
def missed_ref
- execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"])
+ repository.missed_ref(oldrev, newrev).split("\n")
end
private
- def not_in_refs(references)
- return ['--not', '--all'] unless references
- return [] if references.empty?
-
- references.prepend('--not')
- end
-
def execute(args)
- output, status = popen(args, nil, Gitlab::Git::Env.to_env_hash)
-
- unless status.zero?
- raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}"
- end
-
- output.split("\n")
- end
-
- def lazy_execute(args, &lazy_block)
- popen(args, nil, Gitlab::Git::Env.to_env_hash, lazy_block: lazy_block)
- end
-
- def base_args
- [
- Gitlab.config.git.bin_path,
- "--git-dir=#{path_to_repo}",
- 'rev-list'
- ]
+ repository.rev_list(args).split("\n")
end
- def get_objects(args, require_path: nil)
- if block_given?
- lazy_execute(args) do |lazy_output|
- objects = objects_from_output(lazy_output, require_path: require_path)
+ def get_objects(including: [], excluding: [], require_path: nil)
+ opts = { including: including, excluding: excluding, objects: true }
- yield(objects)
- end
- else
- object_output = execute(args)
+ repository.rev_list(opts) do |lazy_output|
+ objects = objects_from_output(lazy_output, require_path: require_path)
- objects_from_output(object_output, require_path: require_path)
+ yield(objects)
end
end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index daa17fc72cf..39040d56971 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -96,11 +96,23 @@ module Gitlab
# :per_page - The number of items per page.
# :limit - Total number of items to return.
def page_versions(page_path, options = {})
- current_page = gollum_page_by_path(page_path)
+ @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled|
+ if is_enabled
+ versions = gitaly_wiki_client.page_versions(page_path, options)
+
+ # Gitaly uses gollum-lib to get the versions. Gollum defaults to 20
+ # per page, but also fetches 20 if `limit` or `per_page` < 20.
+ # Slicing returns an array with the expected number of items.
+ slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page
+ versions[0..slice_bound]
+ else
+ current_page = gollum_page_by_path(page_path)
- commits_from_page(current_page, options).map do |gitlab_git_commit|
- gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
- Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
+ commits_from_page(current_page, options).map do |gitlab_git_commit|
+ gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
+ Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
+ end
+ end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 56f6febe86d..8ec3386184a 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -2,15 +2,19 @@
# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
+ include Gitlab::Utils::StrongMemoize
+
UnauthorizedError = Class.new(StandardError)
NotFoundError = Class.new(StandardError)
+ ProjectCreationError = Class.new(StandardError)
ProjectMovedError = Class.new(NotFoundError)
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
- deploy_key_upload:
- 'This deploy key does not have write access to this project.',
+ auth_upload: 'You are not allowed to upload code.',
+ auth_download: 'You are not allowed to download code.',
+ deploy_key_upload: 'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.',
project_not_found: 'The project you were looking for could not be found.',
account_blocked: 'Your account has been blocked.',
@@ -25,24 +29,31 @@ module Gitlab
PUSH_COMMANDS = %w{ git-receive-pack }.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
- attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path
+ attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path
- def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil)
+ def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil)
@actor = actor
@project = project
@protocol = protocol
- @redirected_path = redirected_path
@authentication_abilities = authentication_abilities
+ @namespace_path = namespace_path
+ @project_path = project_path
+ @redirected_path = redirected_path
end
def check(cmd, changes)
check_protocol!
check_valid_actor!
check_active_user!
- check_project_accessibility!
- check_project_moved!
+ check_authentication_abilities!(cmd)
check_command_disabled!(cmd)
check_command_existence!(cmd)
+ check_db_accessibility!(cmd)
+
+ ensure_project_on_push!(cmd, changes)
+
+ check_project_accessibility!
+ check_project_moved!
check_repository_existence!
case cmd
@@ -95,6 +106,19 @@ module Gitlab
end
end
+ def check_authentication_abilities!(cmd)
+ case cmd
+ when *DOWNLOAD_COMMANDS
+ unless authentication_abilities.include?(:download_code) || authentication_abilities.include?(:build_download_code)
+ raise UnauthorizedError, ERROR_MESSAGES[:auth_download]
+ end
+ when *PUSH_COMMANDS
+ unless authentication_abilities.include?(:push_code)
+ raise UnauthorizedError, ERROR_MESSAGES[:auth_upload]
+ end
+ end
+ end
+
def check_project_accessibility!
if project.blank? || !can_read_project?
raise NotFoundError, ERROR_MESSAGES[:project_not_found]
@@ -104,12 +128,12 @@ module Gitlab
def check_project_moved!
return if redirected_path.nil?
- project_moved = Checks::ProjectMoved.new(project, user, redirected_path, protocol)
+ project_moved = Checks::ProjectMoved.new(project, user, protocol, redirected_path)
if project_moved.permanent_redirect?
- project_moved.add_redirect_message
+ project_moved.add_message
else
- raise ProjectMovedError, project_moved.redirect_message(rejected: true)
+ raise ProjectMovedError, project_moved.message(rejected: true)
end
end
@@ -139,6 +163,40 @@ module Gitlab
end
end
+ def check_db_accessibility!(cmd)
+ return unless receive_pack?(cmd)
+
+ if Gitlab::Database.read_only?
+ raise UnauthorizedError, push_to_read_only_message
+ end
+ end
+
+ def ensure_project_on_push!(cmd, changes)
+ return if project || deploy_key?
+ return unless receive_pack?(cmd) && changes == '_any' && authentication_abilities.include?(:push_code)
+
+ namespace = Namespace.find_by_full_path(namespace_path)
+
+ return unless user&.can?(:create_projects, namespace)
+
+ project_params = {
+ path: project_path,
+ namespace_id: namespace.id,
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE
+ }
+
+ project = Projects::CreateService.new(user, project_params).execute
+
+ unless project.saved?
+ raise ProjectCreationError, "Could not create project: #{project.errors.full_messages.join(', ')}"
+ end
+
+ @project = project
+ user_access.project = @project
+
+ Checks::ProjectCreated.new(project, user, protocol).add_message
+ end
+
def check_repository_existence!
unless project.repository.exists?
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
@@ -146,9 +204,8 @@ module Gitlab
end
def check_download_access!
- return if deploy_key?
-
- passed = user_can_download_code? ||
+ passed = deploy_key? ||
+ user_can_download_code? ||
build_can_download_code? ||
guest_can_download_code?
@@ -162,35 +219,21 @@ module Gitlab
raise UnauthorizedError, ERROR_MESSAGES[:read_only]
end
- if Gitlab::Database.read_only?
- raise UnauthorizedError, push_to_read_only_message
- end
-
if deploy_key
- check_deploy_key_push_access!
+ unless deploy_key.can_push_to?(project)
+ raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
+ end
elsif user
- check_user_push_access!
+ # User access is verified in check_change_access!
else
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
- return if changes.blank? # Allow access.
+ return if changes.blank? # Allow access this is needed for EE.
check_change_access!(changes)
end
- def check_user_push_access!
- unless authentication_abilities.include?(:push_code)
- raise UnauthorizedError, ERROR_MESSAGES[:upload]
- end
- end
-
- def check_deploy_key_push_access!
- unless deploy_key.can_push_to?(project)
- raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
- end
- end
-
def check_change_access!(changes)
changes_list = Gitlab::ChangesList.new(changes)
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 8e87a8cc36f..0d8dd5cb8f4 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -101,6 +101,30 @@ module Gitlab
pages
end
+ # options:
+ # :page - The Integer page number.
+ # :per_page - The number of items per page.
+ # :limit - Total number of items to return.
+ def page_versions(page_path, options)
+ request = Gitaly::WikiGetPageVersionsRequest.new(
+ repository: @gitaly_repo,
+ page_path: encode_binary(page_path),
+ page: options[:page] || 1,
+ per_page: options[:per_page] || Gollum::Page.per_page
+ )
+
+ stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request)
+
+ versions = []
+ stream.each do |message|
+ message.versions.each do |version|
+ versions << new_wiki_page_version(version)
+ end
+ end
+
+ versions
+ end
+
def find_file(name, revision)
request = Gitaly::WikiFindFileRequest.new(
repository: @gitaly_repo,
@@ -141,7 +165,7 @@ module Gitlab
private
- # If a block is given and the yielded value is true, iteration will be
+ # If a block is given and the yielded value is truthy, iteration will be
# stopped early at that point; else the iterator is consumed entirely.
# The iterator is traversed with `next` to allow resuming the iteration.
def wiki_page_from_iterator(iterator)
@@ -158,10 +182,7 @@ module Gitlab
else
wiki_page = GitalyClient::WikiPage.new(page.to_h)
- version = Gitlab::Git::WikiPageVersion.new(
- Gitlab::Git::Commit.decorate(@repository, page.version.commit),
- page.version.format
- )
+ version = new_wiki_page_version(page.version)
end
end
@@ -170,6 +191,13 @@ module Gitlab
[wiki_page, version]
end
+ def new_wiki_page_version(version)
+ Gitlab::Git::WikiPageVersion.new(
+ Gitlab::Git::Commit.decorate(@repository, version.commit),
+ version.format
+ )
+ end
+
def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new(
name: encode_binary(commit_details.name),
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index e40a001d20c..a3e1c66c19f 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -178,7 +178,7 @@ module Gitlab
valid_username = ::Namespace.clean_path(username)
uniquify = Uniquify.new
- valid_username = uniquify.string(valid_username) { |s| !UserPathValidator.valid_path?(s) }
+ valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
name = auth_hash.name
name = valid_username if name.strip.empty?
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 7e5dfd33502..4dc38aae61e 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -171,24 +171,16 @@ module Gitlab
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
end
- def root_namespace_path_regex
- @root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z}
- end
-
def full_namespace_path_regex
@full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
end
- def project_path_regex
- @project_path_regex ||= %r{\A#{project_route_regex}/\z}
- end
-
def full_project_path_regex
@full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z}
end
- def full_namespace_format_regex
- @namespace_format_regex ||= /A#{FULL_NAMESPACE_FORMAT_REGEX}\z/.freeze
+ def full_project_git_path_regex
+ @full_project_git_path_regex ||= %r{\A\/?(?<namespace_path>#{full_namespace_route_regex})\/(?<project_path>#{project_route_regex})\.git\z}
end
def namespace_format_regex
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 2bfb7caefd9..b89ae2505c9 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -45,7 +45,7 @@ module Gitlab
private
def get_rss
- output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}))
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
return 0 unless status.zero?
output.to_i
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index f357488ac61..15eb1c41213 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -6,7 +6,8 @@ module Gitlab
[user&.id, project&.id]
end
- attr_reader :user, :project
+ attr_reader :user
+ attr_accessor :project
def initialize(user, project: nil)
@user = user
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index b3f8b0d174d..823df67ea39 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -161,6 +161,18 @@ module Gitlab
]
end
+ def send_url(url, allow_redirects: false)
+ params = {
+ 'URL' => url,
+ 'AllowRedirects' => allow_redirects
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "send-url:#{encode(params)}"
+ ]
+ end
+
def terminal_websocket(terminal)
details = {
'Terminal' => {
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 159521a750b..d5cb5400d4f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-02-05 16:03+0100\n"
-"PO-Revision-Date: 2018-02-05 16:03+0100\n"
+"POT-Creation-Date: 2018-02-06 10:02+0100\n"
+"PO-Revision-Date: 2018-02-06 10:02+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -153,12 +153,21 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "Allows you to add and manage Kubernetes clusters."
+msgstr ""
+
msgid "An error occurred previewing the blob"
msgstr ""
msgid "An error occurred when toggling the notification subscription"
msgstr ""
+msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
+msgstr ""
+
+msgid "An error occurred while fetching markdown preview"
+msgstr ""
+
msgid "An error occurred while fetching sidebar data"
msgstr ""
@@ -171,12 +180,18 @@ msgstr ""
msgid "An error occurred while rendering KaTeX"
msgstr ""
+msgid "An error occurred while rendering preview broadcast message"
+msgstr ""
+
msgid "An error occurred while retrieving calendar activity"
msgstr ""
msgid "An error occurred while retrieving diff"
msgstr ""
+msgid "An error occurred while validating username"
+msgstr ""
+
msgid "An error occurred. Please try again."
msgstr ""
@@ -392,6 +407,9 @@ msgstr ""
msgid "Cancel edit"
msgstr ""
+msgid "Cannot modify managed Kubernetes cluster"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -524,28 +542,25 @@ msgstr ""
msgid "Clone repository"
msgstr ""
-msgid "Cluster"
-msgstr ""
-
-msgid "ClusterIntegration|%{appList} was successfully installed on your cluster"
+msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|API URL"
msgstr ""
-msgid "ClusterIntegration|Add an existing cluster"
+msgid "ClusterIntegration|Add Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Add cluster"
+msgid "ClusterIntegration|Add an existing Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Advanced options on this cluster's integration"
+msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
msgid "ClusterIntegration|Applications"
msgstr ""
-msgid "ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster."
+msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr ""
msgid "ClusterIntegration|CA Certificate"
@@ -554,121 +569,121 @@ msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
-msgid "ClusterIntegration|Choose how to set up cluster integration"
+msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration"
msgstr ""
-msgid "ClusterIntegration|Choose which of your project's environments will use this cluster."
+msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
msgstr ""
-msgid "ClusterIntegration|Cluster"
+msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
msgstr ""
-msgid "ClusterIntegration|Cluster details"
+msgid "ClusterIntegration|Copy API URL"
msgstr ""
-msgid "ClusterIntegration|Cluster integration"
+msgid "ClusterIntegration|Copy CA Certificate"
msgstr ""
-msgid "ClusterIntegration|Cluster integration is disabled for this project."
+msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Cluster integration is enabled for this project."
+msgid "ClusterIntegration|Copy Token"
msgstr ""
-msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it."
+msgid "ClusterIntegration|Create Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..."
+msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Cluster name"
+msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab"
msgstr ""
-msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details"
+msgid "ClusterIntegration|Create on GKE"
msgstr ""
-msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
+msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project"
+msgid "ClusterIntegration|Enter the details for your Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Control how your cluster integrates with GitLab"
+msgid "ClusterIntegration|Environment scope"
msgstr ""
-msgid "ClusterIntegration|Copy API URL"
+msgid "ClusterIntegration|GitLab Integration"
msgstr ""
-msgid "ClusterIntegration|Copy CA Certificate"
+msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|Copy Token"
+msgid "ClusterIntegration|Google Cloud Platform project ID"
msgstr ""
-msgid "ClusterIntegration|Copy cluster name"
+msgid "ClusterIntegration|Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab"
+msgid "ClusterIntegration|Google Kubernetes Engine project"
msgstr ""
-msgid "ClusterIntegration|Create cluster"
+msgid "ClusterIntegration|Helm Tiller"
msgstr ""
-msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine"
+msgid "ClusterIntegration|Ingress"
msgstr ""
-msgid "ClusterIntegration|Create on GKE"
+msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
+msgid "ClusterIntegration|Installed"
msgstr ""
-msgid "ClusterIntegration|Enter the details for your cluster"
+msgid "ClusterIntegration|Installing"
msgstr ""
-msgid "ClusterIntegration|Environment scope"
+msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
-msgid "ClusterIntegration|GitLab Integration"
+msgid "ClusterIntegration|Integration status"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner"
+msgid "ClusterIntegration|Kubernetes cluster"
msgstr ""
-msgid "ClusterIntegration|Google Cloud Platform project ID"
+msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
-msgid "ClusterIntegration|Google Kubernetes Engine"
+msgid "ClusterIntegration|Kubernetes cluster integration"
msgstr ""
-msgid "ClusterIntegration|Google Kubernetes Engine project"
+msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
msgstr ""
-msgid "ClusterIntegration|Helm Tiller"
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
msgstr ""
-msgid "ClusterIntegration|Ingress"
+msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it."
msgstr ""
-msgid "ClusterIntegration|Install"
+msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
-msgid "ClusterIntegration|Installed"
+msgid "ClusterIntegration|Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Installing"
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
msgstr ""
-msgid "ClusterIntegration|Integrate cluster automation"
+msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
msgstr ""
-msgid "ClusterIntegration|Integration status"
+msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
msgstr ""
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr ""
-msgid "ClusterIntegration|Learn more about Clusters"
+msgid "ClusterIntegration|Learn more about Kubernetes"
msgstr ""
msgid "ClusterIntegration|Learn more about environments"
@@ -677,10 +692,10 @@ msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
-msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters"
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}"
+msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
msgid "ClusterIntegration|Note:"
@@ -689,7 +704,7 @@ msgstr ""
msgid "ClusterIntegration|Number of nodes"
msgstr ""
-msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters"
+msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes"
msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
@@ -707,16 +722,16 @@ msgstr ""
msgid "ClusterIntegration|Prometheus"
msgstr ""
-msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration."
+msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration."
msgstr ""
-msgid "ClusterIntegration|Remove cluster integration"
+msgid "ClusterIntegration|Remove Kubernetes cluster integration"
msgstr ""
msgid "ClusterIntegration|Remove integration"
msgstr ""
-msgid "ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster."
+msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster."
msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
@@ -725,7 +740,7 @@ msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
-msgid "ClusterIntegration|See and edit the details for your cluster"
+msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|See machine types"
@@ -746,22 +761,25 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
-msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine"
+msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
-msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below"
+msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
+msgstr ""
+
+msgid "ClusterIntegration|Toggle Kubernetes Cluster"
msgstr ""
-msgid "ClusterIntegration|Toggle Cluster"
+msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|Token"
msgstr ""
-msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
+msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
@@ -776,9 +794,6 @@ msgstr ""
msgid "ClusterIntegration|check the pricing here"
msgstr ""
-msgid "ClusterIntegration|cluster"
-msgstr ""
-
msgid "ClusterIntegration|documentation"
msgstr ""
@@ -1212,6 +1227,9 @@ msgstr ""
msgid "February"
msgstr ""
+msgid "Fields on this page are now uneditable, you can configure"
+msgstr ""
+
msgid "File name"
msgstr ""
@@ -1280,6 +1298,9 @@ msgstr ""
msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service."
msgstr ""
+msgid "Got it!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -1381,6 +1402,9 @@ msgstr ""
msgid "Install a Runner compatible with GitLab CI"
msgstr ""
+msgid "Instance does not support multiple Kubernetes clusters"
+msgstr ""
+
msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr ""
@@ -1426,6 +1450,27 @@ msgstr ""
msgid "June"
msgstr ""
+msgid "Kubernetes"
+msgstr ""
+
+msgid "Kubernetes Cluster"
+msgstr ""
+
+msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
+msgstr ""
+
+msgid "Kubernetes cluster integration was not removed."
+msgstr ""
+
+msgid "Kubernetes cluster integration was successfully removed."
+msgstr ""
+
+msgid "Kubernetes cluster was successfully updated."
+msgstr ""
+
+msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -1551,14 +1596,17 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
-msgid "New Cluster"
-msgstr ""
-
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
+msgid "New Kubernetes Cluster"
+msgstr ""
+
+msgid "New Kubernetes cluster"
+msgstr ""
+
msgid "New Pipeline Schedule"
msgstr ""
@@ -1835,7 +1883,7 @@ msgstr ""
msgid "Play"
msgstr ""
-msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a cluster</a>, then try again."
+msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
msgstr ""
msgid "Please solve the reCAPTCHA"
@@ -2009,6 +2057,9 @@ msgstr ""
msgid "PrometheusService|View environments"
msgstr ""
+msgid "Protip:"
+msgstr ""
+
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr ""
@@ -2480,9 +2531,15 @@ msgstr ""
msgid "There are problems accessing Git storage: "
msgstr ""
+msgid "There was an error loading users activity calendar."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
+msgid "There was an error subscribing to this label."
+msgstr ""
+
msgid "There was an error when reseting email token."
msgstr ""
@@ -2946,6 +3003,9 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
@@ -3125,3 +3185,6 @@ msgstr ""
msgid "username"
msgstr ""
+
+msgid "uses Kubernetes clusters to deploy your code!"
+msgstr ""
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index a3b13647c92..954fc79f57d 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -177,7 +177,7 @@ describe Projects::ClustersController do
cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster))
- expect(flash[:notice]).to eq('Cluster was successfully updated.')
+ expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
end
@@ -276,7 +276,7 @@ describe Projects::ClustersController do
cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster))
- expect(flash[:notice]).to eq('Cluster was successfully updated.')
+ expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
@@ -336,7 +336,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(project_clusters_path(project))
- expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
@@ -349,7 +349,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(project_clusters_path(project))
- expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
end
@@ -364,7 +364,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(0)
expect(response).to redirect_to(project_clusters_path(project))
- expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
+ expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index db595430979..f3e303bb0fe 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -159,8 +159,19 @@ describe Projects::JobsController do
get_trace
end
+ context 'when job has a trace artifact' do
+ let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ it 'returns a trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq job.id
+ expect(json_response['status']).to eq job.status
+ expect(json_response['html']).to eq(job.trace.html)
+ end
+ end
+
context 'when job has a trace' do
- let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
@@ -182,7 +193,7 @@ describe Projects::JobsController do
end
context 'when job has a trace with ANSI sequence and Unicode' do
- let(:job) { create(:ci_build, :unicode_trace, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
it 'returns a trace with Unicode' do
expect(response).to have_gitlab_http_status(:ok)
@@ -381,7 +392,7 @@ describe Projects::JobsController do
end
context 'when job is erasable' do
- let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline) }
it 'redirects to the erased job page' do
expect(response).to have_gitlab_http_status(:found)
@@ -408,7 +419,7 @@ describe Projects::JobsController do
context 'when user is developer' do
let(:role) { :developer }
- let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline, user: triggered_by) }
+ let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline, user: triggered_by) }
context 'when triggered by same user' do
let(:triggered_by) { user }
@@ -439,8 +450,18 @@ describe Projects::JobsController do
get_raw
end
+ context 'when job has a trace artifact' do
+ let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ it 'returns a trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.content_type).to eq 'text/plain; charset=utf-8'
+ expect(response.body).to eq job.job_artifacts_trace.open.read
+ end
+ end
+
context 'when job has a trace file' do
- let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'send a trace file' do
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 6f66468570f..6ba599cdf83 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -135,13 +135,19 @@ FactoryBot.define do
coverage_regex '/(d+)/'
end
- trait :trace do
+ trait :trace_live do
after(:create) do |build, evaluator|
build.trace.set('BUILD TRACE')
end
end
- trait :unicode_trace do
+ trait :trace_artifact do
+ after(:create) do |build, evaluator|
+ create(:ci_job_artifact, :trace, job: build)
+ end
+ end
+
+ trait :unicode_trace_live do
after(:create) do |build, evaluator|
trace = File.binread(
File.expand_path(
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 46afba2953c..7ee379ca2ec 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -26,5 +26,14 @@ FactoryBot.define do
Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
end
end
+
+ trait :trace do
+ file_type :trace
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
+ end
+ end
end
end
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 9c4abec115f..8d1e10b7191 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -64,7 +64,7 @@ feature 'Clusters Applications', :js do
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
end
- expect(page).to have_content('Helm Tiller was successfully installed on your cluster')
+ expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
end
end
@@ -98,7 +98,7 @@ feature 'Clusters Applications', :js do
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
end
- expect(page).to have_content('Ingress was successfully installed on your cluster')
+ expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster')
end
end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 94bde723e2f..02dbd3380b3 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -32,7 +32,7 @@ feature 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Add cluster'
+ click_link 'Add Kubernetes cluster'
click_link 'Create on GKE'
end
@@ -50,19 +50,19 @@ feature 'Gcp Cluster', :js do
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster'
- click_button 'Create cluster'
+ click_button 'Create Kubernetes cluster'
end
it 'user sees a cluster details page and creation status' do
- expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_created!
- expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine')
+ expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
end
it 'user sees a error if something worng during creation' do
- expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...')
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_errored!('Something wrong!')
@@ -72,7 +72,7 @@ feature 'Gcp Cluster', :js do
context 'when user filled form with invalid parameters' do
before do
- click_button 'Create cluster'
+ click_button 'Create Kubernetes cluster'
end
it 'user sees a validation error' do
@@ -100,7 +100,7 @@ feature 'Gcp Cluster', :js do
end
it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
end
end
@@ -111,7 +111,7 @@ feature 'Gcp Cluster', :js do
end
it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
end
end
@@ -124,8 +124,8 @@ feature 'Gcp Cluster', :js do
end
it 'user sees creation form with the successful message' do
- expect(page).to have_content('Cluster integration was successfully removed.')
- expect(page).to have_link('Add cluster')
+ expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
+ expect(page).to have_link('Add Kubernetes cluster')
end
end
end
@@ -138,16 +138,16 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project)
- click_link 'Add cluster'
+ click_link 'Add Kubernetes cluster'
click_link 'Create on GKE'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster'
- click_button 'Create cluster'
+ click_button 'Create Kubernetes cluster'
end
it 'user sees form with error' do
- expect(page).to have_content('Please enable billing for one of your projects to be able to create a cluster, then try again.')
+ expect(page).to have_content('Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again.')
end
end
@@ -158,12 +158,12 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project)
- click_link 'Add cluster'
+ click_link 'Add Kubernetes cluster'
click_link 'Create on GKE'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster'
- click_button 'Create cluster'
+ click_button 'Create Kubernetes cluster'
end
it 'user sees form with error' do
@@ -176,7 +176,7 @@ feature 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Add cluster'
+ click_link 'Add Kubernetes cluster'
click_link 'Create on GKE'
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index b9ab434c259..698b64a659c 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -16,8 +16,8 @@ feature 'User Cluster', :js do
before do
visit project_clusters_path(project)
- click_link 'Add cluster'
- click_link 'Add an existing cluster'
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add an existing Kubernetes cluster'
end
context 'when user filled form with valid parameters' do
@@ -25,11 +25,11 @@ feature 'User Cluster', :js do
fill_in 'cluster_name', with: 'dev-cluster'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
- click_button 'Add cluster'
+ click_button 'Add Kubernetes cluster'
end
it 'user sees a cluster details page' do
- expect(page).to have_content('Cluster integration')
+ expect(page).to have_content('Kubernetes cluster integration')
expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
.to have_content('http://example.com')
@@ -40,7 +40,7 @@ feature 'User Cluster', :js do
context 'when user filled form with invalid parameters' do
before do
- click_button 'Add cluster'
+ click_button 'Add Kubernetes cluster'
end
it 'user sees a validation error' do
@@ -68,7 +68,7 @@ feature 'User Cluster', :js do
end
it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
end
end
@@ -80,7 +80,7 @@ feature 'User Cluster', :js do
end
it 'user sees the successful message' do
- expect(page).to have_content('Cluster was successfully updated.')
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
expect(cluster.reload.name).to eq('my-dev-cluster')
expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
end
@@ -94,8 +94,8 @@ feature 'User Cluster', :js do
end
it 'user sees creation form with the successful message' do
- expect(page).to have_content('Cluster integration was successfully removed.')
- expect(page).to have_link('Add cluster')
+ expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
+ expect(page).to have_link('Add Kubernetes cluster')
end
end
end
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index 497a50bebe4..bd9f7745cf8 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -17,7 +17,7 @@ feature 'Clusters', :js do
end
it 'sees empty state' do
- expect(page).to have_link('Add cluster')
+ expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_selector('.empty-state')
end
end
@@ -82,7 +82,7 @@ feature 'Clusters', :js do
before do
visit project_clusters_path(project)
- click_link 'Add cluster'
+ click_link 'Add Kubernetes cluster'
click_link 'Create on GKE'
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index e661db1809a..5d311f2dde3 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -7,7 +7,7 @@ feature 'Jobs' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
let(:job2) { create(:ci_build) }
let(:artifacts_file) do
@@ -490,18 +490,34 @@ feature 'Jobs' do
describe 'GET /:project/jobs/:id/raw', :js do
context 'access source' do
context 'job from project' do
- before do
- job.run!
- end
+ context 'when job is running' do
+ before do
+ job.run!
+ end
- it 'sends the right headers' do
- requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
- visit raw_project_job_path(project, job)
+ it 'sends the right headers' do
+ requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
+ visit raw_project_job_path(project, job)
+ end
+
+ expect(requests.first.status_code).to eq(200)
+ expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
end
+ end
- expect(requests.first.status_code).to eq(200)
- expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
+ context 'when job is complete' do
+ let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
+
+ it 'sends the right headers' do
+ requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
+ visit raw_project_job_path(project, job)
+ end
+
+ expect(requests.first.status_code).to eq(200)
+ expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(requests.first.response_headers['X-Sendfile']).to eq(job.job_artifacts_trace.file.path)
+ end
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 85bd776932b..ae8b1364ec7 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -99,7 +99,7 @@ feature 'Prioritize labels' do
expect(page).to have_content 'wontfix'
# Sort labels
- drag_to(selector: '.js-prioritized-labels', from_index: 1, to_index: 2)
+ drag_to(selector: '.label-list-item', from_index: 1, to_index: 2)
page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature')
diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace
new file mode 100644
index 00000000000..55fcb9d2756
--- /dev/null
+++ b/spec/fixtures/trace/sample_trace
@@ -0,0 +1,1185 @@
+Running with gitlab-runner 10.4.0 (857480b6)
+ on docker-auto-scale-com (9a6801bd)
+Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
+Starting service postgres:9.2 ...
+Pulling docker image postgres:9.2 ...
+Using docker image postgres:9.2 ID=sha256:18cdbca56093c841d28e629eb8acd4224afe0aa4c57c839351fc181888b8a470 for postgres service...
+Starting service redis:alpine ...
+Pulling docker image redis:alpine ...
+Using docker image redis:alpine ID=sha256:cb1ec54b370d4a91dff57d00f91fd880dc710160a58440adaa133e0f84ae999d for redis service...
+Waiting for services to be up and running...
+Using docker image sha256:3006a02a5a6f0a116358a13bbc46ee46fb2471175efd5b7f9b1c22345ec2a8e9 for predefined container...
+Pulling docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ...
+Using docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ID=sha256:1f59be408f12738509ffe4177d65e9de6391f32461de83d9d45f58517b30af99 for build container...
+section_start:1517486886:prepare_script
+Running on runner-9a6801bd-project-13083-concurrent-0 via runner-9a6801bd-gsrm-1517484168-a8449153...
+section_end:1517486887:prepare_script
+section_start:1517486887:get_sources
+Fetching changes for 42624-gitaly-bundle-isolation-not-working-in-ci with git depth set to 20...
+Removing .gitlab_shell_secret
+Removing .gitlab_workhorse_secret
+Removing .yarn-cache/
+Removing config/database.yml
+Removing config/gitlab.yml
+Removing config/redis.cache.yml
+Removing config/redis.queues.yml
+Removing config/redis.shared_state.yml
+Removing config/resque.yml
+Removing config/secrets.yml
+Removing coverage/
+Removing knapsack/
+Removing log/api_json.log
+Removing log/application.log
+Removing log/gitaly-test.log
+Removing log/githost.log
+Removing log/grpc.log
+Removing log/test_json.log
+Removing node_modules/
+Removing public/assets/
+Removing rspec_flaky/
+Removing shared/tmp/
+Removing tmp/tests/
+Removing vendor/ruby/
+HEAD is now at 4cea24f Converted todos.js to axios
+From https://gitlab.com/gitlab-org/gitlab-ce
+ * [new branch] 42624-gitaly-bundle-isolation-not-working-in-ci -> origin/42624-gitaly-bundle-isolation-not-working-in-ci
+Checking out f42a5e24 as 42624-gitaly-bundle-isolation-not-working-in-ci...
+Skipping Git submodules setup
+section_end:1517486896:get_sources
+section_start:1517486896:restore_cache
+Checking cache for ruby-2.3.6-with-yarn...
+Downloading cache.zip from http://runners-cache-5-internal.gitlab.com:444/runner/project/13083/ruby-2.3.6-with-yarn
+Successfully extracted cache
+section_end:1517486919:restore_cache
+section_start:1517486919:download_artifacts
+Downloading artifacts for retrieve-tests-metadata (50551658)...
+Downloading artifacts from coordinator... ok  id=50551658 responseStatus=200 OK token=HhF7y_1X
+Downloading artifacts for compile-assets (50551659)...
+Downloading artifacts from coordinator... ok  id=50551659 responseStatus=200 OK token=wTz6JrCP
+Downloading artifacts for setup-test-env (50551660)...
+Downloading artifacts from coordinator... ok  id=50551660 responseStatus=200 OK token=DTGgeVF5
+WARNING: tmp/tests/gitlab-shell/.gitlab_shell_secret: chmod tmp/tests/gitlab-shell/.gitlab_shell_secret: no such file or directory (suppressing repeats)
+section_end:1517486934:download_artifacts
+section_start:1517486934:build_script
+$ bundle --version
+Bundler version 1.16.1
+$ source scripts/utils.sh
+$ source scripts/prepare_build.sh
+The Gemfile's dependencies are satisfied
+Successfully installed knapsack-1.15.0
+1 gem installed
+NOTICE: database "gitlabhq_test" does not exist, skipping
+DROP DATABASE
+CREATE DATABASE
+CREATE ROLE
+GRANT
+-- enable_extension("plpgsql")
+ -> 0.0156s
+-- enable_extension("pg_trgm")
+ -> 0.0156s
+-- create_table("abuse_reports", {:force=>:cascade})
+ -> 0.0119s
+-- create_table("appearances", {:force=>:cascade})
+ -> 0.0065s
+-- create_table("application_settings", {:force=>:cascade})
+ -> 0.0382s
+-- create_table("audit_events", {:force=>:cascade})
+ -> 0.0056s
+-- add_index("audit_events", ["entity_id", "entity_type"], {:name=>"index_audit_events_on_entity_id_and_entity_type", :using=>:btree})
+ -> 0.0040s
+-- create_table("award_emoji", {:force=>:cascade})
+ -> 0.0058s
+-- add_index("award_emoji", ["awardable_type", "awardable_id"], {:name=>"index_award_emoji_on_awardable_type_and_awardable_id", :using=>:btree})
+ -> 0.0068s
+-- add_index("award_emoji", ["user_id", "name"], {:name=>"index_award_emoji_on_user_id_and_name", :using=>:btree})
+ -> 0.0043s
+-- create_table("boards", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("boards", ["project_id"], {:name=>"index_boards_on_project_id", :using=>:btree})
+ -> 0.0056s
+-- create_table("broadcast_messages", {:force=>:cascade})
+ -> 0.0056s
+-- add_index("broadcast_messages", ["starts_at", "ends_at", "id"], {:name=>"index_broadcast_messages_on_starts_at_and_ends_at_and_id", :using=>:btree})
+ -> 0.0041s
+-- create_table("chat_names", {:force=>:cascade})
+ -> 0.0056s
+-- add_index("chat_names", ["service_id", "team_id", "chat_id"], {:name=>"index_chat_names_on_service_id_and_team_id_and_chat_id", :unique=>true, :using=>:btree})
+ -> 0.0039s
+-- add_index("chat_names", ["user_id", "service_id"], {:name=>"index_chat_names_on_user_id_and_service_id", :unique=>true, :using=>:btree})
+ -> 0.0036s
+-- create_table("chat_teams", {:force=>:cascade})
+ -> 0.0068s
+-- add_index("chat_teams", ["namespace_id"], {:name=>"index_chat_teams_on_namespace_id", :unique=>true, :using=>:btree})
+ -> 0.0098s
+-- create_table("ci_build_trace_section_names", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("ci_build_trace_section_names", ["project_id", "name"], {:name=>"index_ci_build_trace_section_names_on_project_id_and_name", :unique=>true, :using=>:btree})
+ -> 0.0035s
+-- create_table("ci_build_trace_sections", {:force=>:cascade})
+ -> 0.0040s
+-- add_index("ci_build_trace_sections", ["build_id", "section_name_id"], {:name=>"index_ci_build_trace_sections_on_build_id_and_section_name_id", :unique=>true, :using=>:btree})
+ -> 0.0035s
+-- add_index("ci_build_trace_sections", ["project_id"], {:name=>"index_ci_build_trace_sections_on_project_id", :using=>:btree})
+ -> 0.0033s
+-- create_table("ci_builds", {:force=>:cascade})
+ -> 0.0062s
+-- add_index("ci_builds", ["auto_canceled_by_id"], {:name=>"index_ci_builds_on_auto_canceled_by_id", :using=>:btree})
+ -> 0.0035s
+-- add_index("ci_builds", ["commit_id", "stage_idx", "created_at"], {:name=>"index_ci_builds_on_commit_id_and_stage_idx_and_created_at", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_builds", ["commit_id", "status", "type"], {:name=>"index_ci_builds_on_commit_id_and_status_and_type", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_builds", ["commit_id", "type", "name", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_name_and_ref", :using=>:btree})
+ -> 0.0035s
+-- add_index("ci_builds", ["commit_id", "type", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_ref", :using=>:btree})
+ -> 0.0042s
+-- add_index("ci_builds", ["project_id", "id"], {:name=>"index_ci_builds_on_project_id_and_id", :using=>:btree})
+ -> 0.0031s
+-- add_index("ci_builds", ["protected"], {:name=>"index_ci_builds_on_protected", :using=>:btree})
+ -> 0.0031s
+-- add_index("ci_builds", ["runner_id"], {:name=>"index_ci_builds_on_runner_id", :using=>:btree})
+ -> 0.0033s
+-- add_index("ci_builds", ["stage_id"], {:name=>"index_ci_builds_on_stage_id", :using=>:btree})
+ -> 0.0035s
+-- add_index("ci_builds", ["status", "type", "runner_id"], {:name=>"index_ci_builds_on_status_and_type_and_runner_id", :using=>:btree})
+ -> 0.0031s
+-- add_index("ci_builds", ["status"], {:name=>"index_ci_builds_on_status", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_builds", ["token"], {:name=>"index_ci_builds_on_token", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("ci_builds", ["updated_at"], {:name=>"index_ci_builds_on_updated_at", :using=>:btree})
+ -> 0.0047s
+-- add_index("ci_builds", ["user_id"], {:name=>"index_ci_builds_on_user_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("ci_group_variables", {:force=>:cascade})
+ -> 0.0055s
+-- add_index("ci_group_variables", ["group_id", "key"], {:name=>"index_ci_group_variables_on_group_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- create_table("ci_job_artifacts", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("ci_job_artifacts", ["job_id", "file_type"], {:name=>"index_ci_job_artifacts_on_job_id_and_file_type", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- add_index("ci_job_artifacts", ["project_id"], {:name=>"index_ci_job_artifacts_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("ci_pipeline_schedule_variables", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], {:name=>"index_ci_pipeline_schedule_variables_on_schedule_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- create_table("ci_pipeline_schedules", {:force=>:cascade})
+ -> 0.0047s
+-- add_index("ci_pipeline_schedules", ["next_run_at", "active"], {:name=>"index_ci_pipeline_schedules_on_next_run_at_and_active", :using=>:btree})
+ -> 0.0029s
+-- add_index("ci_pipeline_schedules", ["project_id"], {:name=>"index_ci_pipeline_schedules_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("ci_pipeline_variables", {:force=>:cascade})
+ -> 0.0045s
+-- add_index("ci_pipeline_variables", ["pipeline_id", "key"], {:name=>"index_ci_pipeline_variables_on_pipeline_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- create_table("ci_pipelines", {:force=>:cascade})
+ -> 0.0057s
+-- add_index("ci_pipelines", ["auto_canceled_by_id"], {:name=>"index_ci_pipelines_on_auto_canceled_by_id", :using=>:btree})
+ -> 0.0030s
+-- add_index("ci_pipelines", ["pipeline_schedule_id"], {:name=>"index_ci_pipelines_on_pipeline_schedule_id", :using=>:btree})
+ -> 0.0031s
+-- add_index("ci_pipelines", ["project_id", "ref", "status", "id"], {:name=>"index_ci_pipelines_on_project_id_and_ref_and_status_and_id", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_pipelines", ["project_id", "sha"], {:name=>"index_ci_pipelines_on_project_id_and_sha", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_pipelines", ["project_id"], {:name=>"index_ci_pipelines_on_project_id", :using=>:btree})
+ -> 0.0035s
+-- add_index("ci_pipelines", ["status"], {:name=>"index_ci_pipelines_on_status", :using=>:btree})
+ -> 0.0032s
+-- add_index("ci_pipelines", ["user_id"], {:name=>"index_ci_pipelines_on_user_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("ci_runner_projects", {:force=>:cascade})
+ -> 0.0035s
+-- add_index("ci_runner_projects", ["project_id"], {:name=>"index_ci_runner_projects_on_project_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("ci_runner_projects", ["runner_id"], {:name=>"index_ci_runner_projects_on_runner_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("ci_runners", {:force=>:cascade})
+ -> 0.0059s
+-- add_index("ci_runners", ["contacted_at"], {:name=>"index_ci_runners_on_contacted_at", :using=>:btree})
+ -> 0.0030s
+-- add_index("ci_runners", ["is_shared"], {:name=>"index_ci_runners_on_is_shared", :using=>:btree})
+ -> 0.0030s
+-- add_index("ci_runners", ["locked"], {:name=>"index_ci_runners_on_locked", :using=>:btree})
+ -> 0.0030s
+-- add_index("ci_runners", ["token"], {:name=>"index_ci_runners_on_token", :using=>:btree})
+ -> 0.0029s
+-- create_table("ci_stages", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("ci_stages", ["pipeline_id", "name"], {:name=>"index_ci_stages_on_pipeline_id_and_name", :using=>:btree})
+ -> 0.0031s
+-- add_index("ci_stages", ["pipeline_id"], {:name=>"index_ci_stages_on_pipeline_id", :using=>:btree})
+ -> 0.0030s
+-- add_index("ci_stages", ["project_id"], {:name=>"index_ci_stages_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("ci_trigger_requests", {:force=>:cascade})
+ -> 0.0058s
+-- add_index("ci_trigger_requests", ["commit_id"], {:name=>"index_ci_trigger_requests_on_commit_id", :using=>:btree})
+ -> 0.0031s
+-- create_table("ci_triggers", {:force=>:cascade})
+ -> 0.0043s
+-- add_index("ci_triggers", ["project_id"], {:name=>"index_ci_triggers_on_project_id", :using=>:btree})
+ -> 0.0033s
+-- create_table("ci_variables", {:force=>:cascade})
+ -> 0.0059s
+-- add_index("ci_variables", ["project_id", "key", "environment_scope"], {:name=>"index_ci_variables_on_project_id_and_key_and_environment_scope", :unique=>true, :using=>:btree})
+ -> 0.0031s
+-- create_table("cluster_platforms_kubernetes", {:force=>:cascade})
+ -> 0.0053s
+-- add_index("cluster_platforms_kubernetes", ["cluster_id"], {:name=>"index_cluster_platforms_kubernetes_on_cluster_id", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- create_table("cluster_projects", {:force=>:cascade})
+ -> 0.0032s
+-- add_index("cluster_projects", ["cluster_id"], {:name=>"index_cluster_projects_on_cluster_id", :using=>:btree})
+ -> 0.0035s
+-- add_index("cluster_projects", ["project_id"], {:name=>"index_cluster_projects_on_project_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("cluster_providers_gcp", {:force=>:cascade})
+ -> 0.0051s
+-- add_index("cluster_providers_gcp", ["cluster_id"], {:name=>"index_cluster_providers_gcp_on_cluster_id", :unique=>true, :using=>:btree})
+ -> 0.0034s
+-- create_table("clusters", {:force=>:cascade})
+ -> 0.0052s
+-- add_index("clusters", ["enabled"], {:name=>"index_clusters_on_enabled", :using=>:btree})
+ -> 0.0031s
+-- add_index("clusters", ["user_id"], {:name=>"index_clusters_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("clusters_applications_helm", {:force=>:cascade})
+ -> 0.0045s
+-- create_table("clusters_applications_ingress", {:force=>:cascade})
+ -> 0.0044s
+-- create_table("clusters_applications_prometheus", {:force=>:cascade})
+ -> 0.0047s
+-- create_table("container_repositories", {:force=>:cascade})
+ -> 0.0050s
+-- add_index("container_repositories", ["project_id", "name"], {:name=>"index_container_repositories_on_project_id_and_name", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("container_repositories", ["project_id"], {:name=>"index_container_repositories_on_project_id", :using=>:btree})
+ -> 0.0032s
+-- create_table("conversational_development_index_metrics", {:force=>:cascade})
+ -> 0.0076s
+-- create_table("deploy_keys_projects", {:force=>:cascade})
+ -> 0.0037s
+-- add_index("deploy_keys_projects", ["project_id"], {:name=>"index_deploy_keys_projects_on_project_id", :using=>:btree})
+ -> 0.0032s
+-- create_table("deployments", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("deployments", ["created_at"], {:name=>"index_deployments_on_created_at", :using=>:btree})
+ -> 0.0034s
+-- add_index("deployments", ["environment_id", "id"], {:name=>"index_deployments_on_environment_id_and_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("deployments", ["environment_id", "iid", "project_id"], {:name=>"index_deployments_on_environment_id_and_iid_and_project_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("deployments", ["project_id", "iid"], {:name=>"index_deployments_on_project_id_and_iid", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- create_table("emails", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("emails", ["confirmation_token"], {:name=>"index_emails_on_confirmation_token", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("emails", ["email"], {:name=>"index_emails_on_email", :unique=>true, :using=>:btree})
+ -> 0.0035s
+-- add_index("emails", ["user_id"], {:name=>"index_emails_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("environments", {:force=>:cascade})
+ -> 0.0052s
+-- add_index("environments", ["project_id", "name"], {:name=>"index_environments_on_project_id_and_name", :unique=>true, :using=>:btree})
+ -> 0.0031s
+-- add_index("environments", ["project_id", "slug"], {:name=>"index_environments_on_project_id_and_slug", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- create_table("events", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("events", ["action"], {:name=>"index_events_on_action", :using=>:btree})
+ -> 0.0032s
+-- add_index("events", ["author_id"], {:name=>"index_events_on_author_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("events", ["project_id", "id"], {:name=>"index_events_on_project_id_and_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("events", ["target_type", "target_id"], {:name=>"index_events_on_target_type_and_target_id", :using=>:btree})
+ -> 0.0027s
+-- create_table("feature_gates", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("feature_gates", ["feature_key", "key", "value"], {:name=>"index_feature_gates_on_feature_key_and_key_and_value", :unique=>true, :using=>:btree})
+ -> 0.0031s
+-- create_table("features", {:force=>:cascade})
+ -> 0.0041s
+-- add_index("features", ["key"], {:name=>"index_features_on_key", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- create_table("fork_network_members", {:force=>:cascade})
+ -> 0.0033s
+-- add_index("fork_network_members", ["fork_network_id"], {:name=>"index_fork_network_members_on_fork_network_id", :using=>:btree})
+ -> 0.0033s
+-- add_index("fork_network_members", ["project_id"], {:name=>"index_fork_network_members_on_project_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("fork_networks", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("fork_networks", ["root_project_id"], {:name=>"index_fork_networks_on_root_project_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("forked_project_links", {:force=>:cascade})
+ -> 0.0032s
+-- add_index("forked_project_links", ["forked_to_project_id"], {:name=>"index_forked_project_links_on_forked_to_project_id", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- create_table("gcp_clusters", {:force=>:cascade})
+ -> 0.0074s
+-- add_index("gcp_clusters", ["project_id"], {:name=>"index_gcp_clusters_on_project_id", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- create_table("gpg_key_subkeys", {:force=>:cascade})
+ -> 0.0042s
+-- add_index("gpg_key_subkeys", ["fingerprint"], {:name=>"index_gpg_key_subkeys_on_fingerprint", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- add_index("gpg_key_subkeys", ["gpg_key_id"], {:name=>"index_gpg_key_subkeys_on_gpg_key_id", :using=>:btree})
+ -> 0.0032s
+-- add_index("gpg_key_subkeys", ["keyid"], {:name=>"index_gpg_key_subkeys_on_keyid", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- create_table("gpg_keys", {:force=>:cascade})
+ -> 0.0042s
+-- add_index("gpg_keys", ["fingerprint"], {:name=>"index_gpg_keys_on_fingerprint", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("gpg_keys", ["primary_keyid"], {:name=>"index_gpg_keys_on_primary_keyid", :unique=>true, :using=>:btree})
+ -> 0.0026s
+-- add_index("gpg_keys", ["user_id"], {:name=>"index_gpg_keys_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("gpg_signatures", {:force=>:cascade})
+ -> 0.0054s
+-- add_index("gpg_signatures", ["commit_sha"], {:name=>"index_gpg_signatures_on_commit_sha", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- add_index("gpg_signatures", ["gpg_key_id"], {:name=>"index_gpg_signatures_on_gpg_key_id", :using=>:btree})
+ -> 0.0026s
+-- add_index("gpg_signatures", ["gpg_key_primary_keyid"], {:name=>"index_gpg_signatures_on_gpg_key_primary_keyid", :using=>:btree})
+ -> 0.0029s
+-- add_index("gpg_signatures", ["gpg_key_subkey_id"], {:name=>"index_gpg_signatures_on_gpg_key_subkey_id", :using=>:btree})
+ -> 0.0032s
+-- add_index("gpg_signatures", ["project_id"], {:name=>"index_gpg_signatures_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("group_custom_attributes", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("group_custom_attributes", ["group_id", "key"], {:name=>"index_group_custom_attributes_on_group_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("group_custom_attributes", ["key", "value"], {:name=>"index_group_custom_attributes_on_key_and_value", :using=>:btree})
+ -> 0.0028s
+-- create_table("identities", {:force=>:cascade})
+ -> 0.0043s
+-- add_index("identities", ["user_id"], {:name=>"index_identities_on_user_id", :using=>:btree})
+ -> 0.0034s
+-- create_table("issue_assignees", {:id=>false, :force=>:cascade})
+ -> 0.0013s
+-- add_index("issue_assignees", ["issue_id", "user_id"], {:name=>"index_issue_assignees_on_issue_id_and_user_id", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("issue_assignees", ["user_id"], {:name=>"index_issue_assignees_on_user_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("issue_metrics", {:force=>:cascade})
+ -> 0.0032s
+-- add_index("issue_metrics", ["issue_id"], {:name=>"index_issue_metrics", :using=>:btree})
+ -> 0.0029s
+-- create_table("issues", {:force=>:cascade})
+ -> 0.0051s
+-- add_index("issues", ["author_id"], {:name=>"index_issues_on_author_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("issues", ["confidential"], {:name=>"index_issues_on_confidential", :using=>:btree})
+ -> 0.0029s
+-- add_index("issues", ["description"], {:name=>"index_issues_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
+ -> 0.0022s
+-- add_index("issues", ["milestone_id"], {:name=>"index_issues_on_milestone_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("issues", ["moved_to_id"], {:name=>"index_issues_on_moved_to_id", :where=>"(moved_to_id IS NOT NULL)", :using=>:btree})
+ -> 0.0030s
+-- add_index("issues", ["project_id", "created_at", "id", "state"], {:name=>"index_issues_on_project_id_and_created_at_and_id_and_state", :using=>:btree})
+ -> 0.0039s
+-- add_index("issues", ["project_id", "due_date", "id", "state"], {:name=>"idx_issues_on_project_id_and_due_date_and_id_and_state_partial", :where=>"(due_date IS NOT NULL)", :using=>:btree})
+ -> 0.0031s
+-- add_index("issues", ["project_id", "iid"], {:name=>"index_issues_on_project_id_and_iid", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("issues", ["project_id", "updated_at", "id", "state"], {:name=>"index_issues_on_project_id_and_updated_at_and_id_and_state", :using=>:btree})
+ -> 0.0035s
+-- add_index("issues", ["relative_position"], {:name=>"index_issues_on_relative_position", :using=>:btree})
+ -> 0.0030s
+-- add_index("issues", ["state"], {:name=>"index_issues_on_state", :using=>:btree})
+ -> 0.0027s
+-- add_index("issues", ["title"], {:name=>"index_issues_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
+ -> 0.0021s
+-- add_index("issues", ["updated_at"], {:name=>"index_issues_on_updated_at", :using=>:btree})
+ -> 0.0030s
+-- add_index("issues", ["updated_by_id"], {:name=>"index_issues_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree})
+ -> 0.0028s
+-- create_table("keys", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("keys", ["fingerprint"], {:name=>"index_keys_on_fingerprint", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("keys", ["user_id"], {:name=>"index_keys_on_user_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("label_links", {:force=>:cascade})
+ -> 0.0041s
+-- add_index("label_links", ["label_id"], {:name=>"index_label_links_on_label_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("label_links", ["target_id", "target_type"], {:name=>"index_label_links_on_target_id_and_target_type", :using=>:btree})
+ -> 0.0028s
+-- create_table("label_priorities", {:force=>:cascade})
+ -> 0.0031s
+-- add_index("label_priorities", ["priority"], {:name=>"index_label_priorities_on_priority", :using=>:btree})
+ -> 0.0028s
+-- add_index("label_priorities", ["project_id", "label_id"], {:name=>"index_label_priorities_on_project_id_and_label_id", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- create_table("labels", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("labels", ["group_id", "project_id", "title"], {:name=>"index_labels_on_group_id_and_project_id_and_title", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("labels", ["project_id"], {:name=>"index_labels_on_project_id", :using=>:btree})
+ -> 0.0032s
+-- add_index("labels", ["template"], {:name=>"index_labels_on_template", :where=>"template", :using=>:btree})
+ -> 0.0027s
+-- add_index("labels", ["title"], {:name=>"index_labels_on_title", :using=>:btree})
+ -> 0.0030s
+-- add_index("labels", ["type", "project_id"], {:name=>"index_labels_on_type_and_project_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("lfs_objects", {:force=>:cascade})
+ -> 0.0040s
+-- add_index("lfs_objects", ["oid"], {:name=>"index_lfs_objects_on_oid", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- create_table("lfs_objects_projects", {:force=>:cascade})
+ -> 0.0035s
+-- add_index("lfs_objects_projects", ["project_id"], {:name=>"index_lfs_objects_projects_on_project_id", :using=>:btree})
+ -> 0.0025s
+-- create_table("lists", {:force=>:cascade})
+ -> 0.0033s
+-- add_index("lists", ["board_id", "label_id"], {:name=>"index_lists_on_board_id_and_label_id", :unique=>true, :using=>:btree})
+ -> 0.0026s
+-- add_index("lists", ["label_id"], {:name=>"index_lists_on_label_id", :using=>:btree})
+ -> 0.0026s
+-- create_table("members", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("members", ["access_level"], {:name=>"index_members_on_access_level", :using=>:btree})
+ -> 0.0028s
+-- add_index("members", ["invite_token"], {:name=>"index_members_on_invite_token", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- add_index("members", ["requested_at"], {:name=>"index_members_on_requested_at", :using=>:btree})
+ -> 0.0025s
+-- add_index("members", ["source_id", "source_type"], {:name=>"index_members_on_source_id_and_source_type", :using=>:btree})
+ -> 0.0027s
+-- add_index("members", ["user_id"], {:name=>"index_members_on_user_id", :using=>:btree})
+ -> 0.0026s
+-- create_table("merge_request_diff_commits", {:id=>false, :force=>:cascade})
+ -> 0.0027s
+-- add_index("merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], {:name=>"index_merge_request_diff_commits_on_mr_diff_id_and_order", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("merge_request_diff_commits", ["sha"], {:name=>"index_merge_request_diff_commits_on_sha", :using=>:btree})
+ -> 0.0029s
+-- create_table("merge_request_diff_files", {:id=>false, :force=>:cascade})
+ -> 0.0027s
+-- add_index("merge_request_diff_files", ["merge_request_diff_id", "relative_order"], {:name=>"index_merge_request_diff_files_on_mr_diff_id_and_order", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- create_table("merge_request_diffs", {:force=>:cascade})
+ -> 0.0042s
+-- add_index("merge_request_diffs", ["merge_request_id", "id"], {:name=>"index_merge_request_diffs_on_merge_request_id_and_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("merge_request_metrics", {:force=>:cascade})
+ -> 0.0034s
+-- add_index("merge_request_metrics", ["first_deployed_to_production_at"], {:name=>"index_merge_request_metrics_on_first_deployed_to_production_at", :using=>:btree})
+ -> 0.0028s
+-- add_index("merge_request_metrics", ["merge_request_id"], {:name=>"index_merge_request_metrics", :using=>:btree})
+ -> 0.0025s
+-- add_index("merge_request_metrics", ["pipeline_id"], {:name=>"index_merge_request_metrics_on_pipeline_id", :using=>:btree})
+ -> 0.0026s
+-- create_table("merge_requests", {:force=>:cascade})
+ -> 0.0066s
+-- add_index("merge_requests", ["assignee_id"], {:name=>"index_merge_requests_on_assignee_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("merge_requests", ["author_id"], {:name=>"index_merge_requests_on_author_id", :using=>:btree})
+ -> 0.0026s
+-- add_index("merge_requests", ["created_at"], {:name=>"index_merge_requests_on_created_at", :using=>:btree})
+ -> 0.0026s
+-- add_index("merge_requests", ["description"], {:name=>"index_merge_requests_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
+ -> 0.0020s
+-- add_index("merge_requests", ["head_pipeline_id"], {:name=>"index_merge_requests_on_head_pipeline_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("merge_requests", ["latest_merge_request_diff_id"], {:name=>"index_merge_requests_on_latest_merge_request_diff_id", :using=>:btree})
+ -> 0.0025s
+-- add_index("merge_requests", ["merge_user_id"], {:name=>"index_merge_requests_on_merge_user_id", :where=>"(merge_user_id IS NOT NULL)", :using=>:btree})
+ -> 0.0029s
+-- add_index("merge_requests", ["milestone_id"], {:name=>"index_merge_requests_on_milestone_id", :using=>:btree})
+ -> 0.0030s
+-- add_index("merge_requests", ["source_branch"], {:name=>"index_merge_requests_on_source_branch", :using=>:btree})
+ -> 0.0026s
+-- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_and_branch_state_opened", :where=>"((state)::text = 'opened'::text)", :using=>:btree})
+ -> 0.0029s
+-- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_id_and_source_branch", :using=>:btree})
+ -> 0.0031s
+-- add_index("merge_requests", ["target_branch"], {:name=>"index_merge_requests_on_target_branch", :using=>:btree})
+ -> 0.0028s
+-- add_index("merge_requests", ["target_project_id", "iid"], {:name=>"index_merge_requests_on_target_project_id_and_iid", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- add_index("merge_requests", ["target_project_id", "merge_commit_sha", "id"], {:name=>"index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title", :using=>:btree})
+ -> 0.0026s
+-- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
+ -> 0.0020s
+-- add_index("merge_requests", ["updated_by_id"], {:name=>"index_merge_requests_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree})
+ -> 0.0029s
+-- create_table("merge_requests_closing_issues", {:force=>:cascade})
+ -> 0.0031s
+-- add_index("merge_requests_closing_issues", ["issue_id"], {:name=>"index_merge_requests_closing_issues_on_issue_id", :using=>:btree})
+ -> 0.0026s
+-- add_index("merge_requests_closing_issues", ["merge_request_id"], {:name=>"index_merge_requests_closing_issues_on_merge_request_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("milestones", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("milestones", ["description"], {:name=>"index_milestones_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
+ -> 0.0022s
+-- add_index("milestones", ["due_date"], {:name=>"index_milestones_on_due_date", :using=>:btree})
+ -> 0.0033s
+-- add_index("milestones", ["group_id"], {:name=>"index_milestones_on_group_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("milestones", ["project_id", "iid"], {:name=>"index_milestones_on_project_id_and_iid", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("milestones", ["title"], {:name=>"index_milestones_on_title", :using=>:btree})
+ -> 0.0026s
+-- add_index("milestones", ["title"], {:name=>"index_milestones_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
+ -> 0.0021s
+-- create_table("namespaces", {:force=>:cascade})
+ -> 0.0068s
+-- add_index("namespaces", ["created_at"], {:name=>"index_namespaces_on_created_at", :using=>:btree})
+ -> 0.0030s
+-- add_index("namespaces", ["name", "parent_id"], {:name=>"index_namespaces_on_name_and_parent_id", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("namespaces", ["name"], {:name=>"index_namespaces_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
+ -> 0.0020s
+-- add_index("namespaces", ["owner_id"], {:name=>"index_namespaces_on_owner_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("namespaces", ["parent_id", "id"], {:name=>"index_namespaces_on_parent_id_and_id", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path", :using=>:btree})
+ -> 0.0031s
+-- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}})
+ -> 0.0019s
+-- add_index("namespaces", ["require_two_factor_authentication"], {:name=>"index_namespaces_on_require_two_factor_authentication", :using=>:btree})
+ -> 0.0029s
+-- add_index("namespaces", ["type"], {:name=>"index_namespaces_on_type", :using=>:btree})
+ -> 0.0032s
+-- create_table("notes", {:force=>:cascade})
+ -> 0.0055s
+-- add_index("notes", ["author_id"], {:name=>"index_notes_on_author_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("notes", ["commit_id"], {:name=>"index_notes_on_commit_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("notes", ["created_at"], {:name=>"index_notes_on_created_at", :using=>:btree})
+ -> 0.0029s
+-- add_index("notes", ["discussion_id"], {:name=>"index_notes_on_discussion_id", :using=>:btree})
+ -> 0.0029s
+-- add_index("notes", ["line_code"], {:name=>"index_notes_on_line_code", :using=>:btree})
+ -> 0.0029s
+-- add_index("notes", ["note"], {:name=>"index_notes_on_note_trigram", :using=>:gin, :opclasses=>{"note"=>"gin_trgm_ops"}})
+ -> 0.0024s
+-- add_index("notes", ["noteable_id", "noteable_type"], {:name=>"index_notes_on_noteable_id_and_noteable_type", :using=>:btree})
+ -> 0.0029s
+-- add_index("notes", ["noteable_type"], {:name=>"index_notes_on_noteable_type", :using=>:btree})
+ -> 0.0030s
+-- add_index("notes", ["project_id", "noteable_type"], {:name=>"index_notes_on_project_id_and_noteable_type", :using=>:btree})
+ -> 0.0027s
+-- add_index("notes", ["updated_at"], {:name=>"index_notes_on_updated_at", :using=>:btree})
+ -> 0.0026s
+-- create_table("notification_settings", {:force=>:cascade})
+ -> 0.0053s
+-- add_index("notification_settings", ["source_id", "source_type"], {:name=>"index_notification_settings_on_source_id_and_source_type", :using=>:btree})
+ -> 0.0028s
+-- add_index("notification_settings", ["user_id", "source_id", "source_type"], {:name=>"index_notifications_on_user_id_and_source_id_and_source_type", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("notification_settings", ["user_id"], {:name=>"index_notification_settings_on_user_id", :using=>:btree})
+ -> 0.0031s
+-- create_table("oauth_access_grants", {:force=>:cascade})
+ -> 0.0042s
+-- add_index("oauth_access_grants", ["token"], {:name=>"index_oauth_access_grants_on_token", :unique=>true, :using=>:btree})
+ -> 0.0031s
+-- create_table("oauth_access_tokens", {:force=>:cascade})
+ -> 0.0051s
+-- add_index("oauth_access_tokens", ["refresh_token"], {:name=>"index_oauth_access_tokens_on_refresh_token", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("oauth_access_tokens", ["resource_owner_id"], {:name=>"index_oauth_access_tokens_on_resource_owner_id", :using=>:btree})
+ -> 0.0025s
+-- add_index("oauth_access_tokens", ["token"], {:name=>"index_oauth_access_tokens_on_token", :unique=>true, :using=>:btree})
+ -> 0.0026s
+-- create_table("oauth_applications", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("oauth_applications", ["owner_id", "owner_type"], {:name=>"index_oauth_applications_on_owner_id_and_owner_type", :using=>:btree})
+ -> 0.0030s
+-- add_index("oauth_applications", ["uid"], {:name=>"index_oauth_applications_on_uid", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- create_table("oauth_openid_requests", {:force=>:cascade})
+ -> 0.0048s
+-- create_table("pages_domains", {:force=>:cascade})
+ -> 0.0052s
+-- add_index("pages_domains", ["domain"], {:name=>"index_pages_domains_on_domain", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- add_index("pages_domains", ["project_id"], {:name=>"index_pages_domains_on_project_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("personal_access_tokens", {:force=>:cascade})
+ -> 0.0056s
+-- add_index("personal_access_tokens", ["token"], {:name=>"index_personal_access_tokens_on_token", :unique=>true, :using=>:btree})
+ -> 0.0032s
+-- add_index("personal_access_tokens", ["user_id"], {:name=>"index_personal_access_tokens_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("project_authorizations", {:id=>false, :force=>:cascade})
+ -> 0.0018s
+-- add_index("project_authorizations", ["project_id"], {:name=>"index_project_authorizations_on_project_id", :using=>:btree})
+ -> 0.0033s
+-- add_index("project_authorizations", ["user_id", "project_id", "access_level"], {:name=>"index_project_authorizations_on_user_id_project_id_access_level", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("project_auto_devops", {:force=>:cascade})
+ -> 0.0043s
+-- add_index("project_auto_devops", ["project_id"], {:name=>"index_project_auto_devops_on_project_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("project_custom_attributes", {:force=>:cascade})
+ -> 0.0047s
+-- add_index("project_custom_attributes", ["key", "value"], {:name=>"index_project_custom_attributes_on_key_and_value", :using=>:btree})
+ -> 0.0030s
+-- add_index("project_custom_attributes", ["project_id", "key"], {:name=>"index_project_custom_attributes_on_project_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- create_table("project_features", {:force=>:cascade})
+ -> 0.0038s
+-- add_index("project_features", ["project_id"], {:name=>"index_project_features_on_project_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("project_group_links", {:force=>:cascade})
+ -> 0.0036s
+-- add_index("project_group_links", ["group_id"], {:name=>"index_project_group_links_on_group_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("project_group_links", ["project_id"], {:name=>"index_project_group_links_on_project_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("project_import_data", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("project_import_data", ["project_id"], {:name=>"index_project_import_data_on_project_id", :using=>:btree})
+ -> 0.0027s
+-- create_table("project_statistics", {:force=>:cascade})
+ -> 0.0046s
+-- add_index("project_statistics", ["namespace_id"], {:name=>"index_project_statistics_on_namespace_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("project_statistics", ["project_id"], {:name=>"index_project_statistics_on_project_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("projects", {:force=>:cascade})
+ -> 0.0090s
+-- add_index("projects", ["ci_id"], {:name=>"index_projects_on_ci_id", :using=>:btree})
+ -> 0.0033s
+-- add_index("projects", ["created_at"], {:name=>"index_projects_on_created_at", :using=>:btree})
+ -> 0.0030s
+-- add_index("projects", ["creator_id"], {:name=>"index_projects_on_creator_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("projects", ["description"], {:name=>"index_projects_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}})
+ -> 0.0022s
+-- add_index("projects", ["last_activity_at"], {:name=>"index_projects_on_last_activity_at", :using=>:btree})
+ -> 0.0032s
+-- add_index("projects", ["last_repository_check_failed"], {:name=>"index_projects_on_last_repository_check_failed", :using=>:btree})
+ -> 0.0030s
+-- add_index("projects", ["last_repository_updated_at"], {:name=>"index_projects_on_last_repository_updated_at", :using=>:btree})
+ -> 0.0031s
+-- add_index("projects", ["name"], {:name=>"index_projects_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
+ -> 0.0022s
+-- add_index("projects", ["namespace_id"], {:name=>"index_projects_on_namespace_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("projects", ["path"], {:name=>"index_projects_on_path", :using=>:btree})
+ -> 0.0028s
+-- add_index("projects", ["path"], {:name=>"index_projects_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}})
+ -> 0.0023s
+-- add_index("projects", ["pending_delete"], {:name=>"index_projects_on_pending_delete", :using=>:btree})
+ -> 0.0029s
+-- add_index("projects", ["repository_storage"], {:name=>"index_projects_on_repository_storage", :using=>:btree})
+ -> 0.0026s
+-- add_index("projects", ["runners_token"], {:name=>"index_projects_on_runners_token", :using=>:btree})
+ -> 0.0034s
+-- add_index("projects", ["star_count"], {:name=>"index_projects_on_star_count", :using=>:btree})
+ -> 0.0028s
+-- add_index("projects", ["visibility_level"], {:name=>"index_projects_on_visibility_level", :using=>:btree})
+ -> 0.0027s
+-- create_table("protected_branch_merge_access_levels", {:force=>:cascade})
+ -> 0.0042s
+-- add_index("protected_branch_merge_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_merge_access", :using=>:btree})
+ -> 0.0029s
+-- create_table("protected_branch_push_access_levels", {:force=>:cascade})
+ -> 0.0037s
+-- add_index("protected_branch_push_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_push_access", :using=>:btree})
+ -> 0.0030s
+-- create_table("protected_branches", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("protected_branches", ["project_id"], {:name=>"index_protected_branches_on_project_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("protected_tag_create_access_levels", {:force=>:cascade})
+ -> 0.0037s
+-- add_index("protected_tag_create_access_levels", ["protected_tag_id"], {:name=>"index_protected_tag_create_access", :using=>:btree})
+ -> 0.0029s
+-- add_index("protected_tag_create_access_levels", ["user_id"], {:name=>"index_protected_tag_create_access_levels_on_user_id", :using=>:btree})
+ -> 0.0029s
+-- create_table("protected_tags", {:force=>:cascade})
+ -> 0.0051s
+-- add_index("protected_tags", ["project_id"], {:name=>"index_protected_tags_on_project_id", :using=>:btree})
+ -> 0.0034s
+-- create_table("push_event_payloads", {:id=>false, :force=>:cascade})
+ -> 0.0030s
+-- add_index("push_event_payloads", ["event_id"], {:name=>"index_push_event_payloads_on_event_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("redirect_routes", {:force=>:cascade})
+ -> 0.0049s
+-- add_index("redirect_routes", ["path"], {:name=>"index_redirect_routes_on_path", :unique=>true, :using=>:btree})
+ -> 0.0031s
+-- add_index("redirect_routes", ["source_type", "source_id"], {:name=>"index_redirect_routes_on_source_type_and_source_id", :using=>:btree})
+ -> 0.0034s
+-- create_table("releases", {:force=>:cascade})
+ -> 0.0043s
+-- add_index("releases", ["project_id", "tag"], {:name=>"index_releases_on_project_id_and_tag", :using=>:btree})
+ -> 0.0032s
+-- add_index("releases", ["project_id"], {:name=>"index_releases_on_project_id", :using=>:btree})
+ -> 0.0030s
+-- create_table("routes", {:force=>:cascade})
+ -> 0.0055s
+-- add_index("routes", ["path"], {:name=>"index_routes_on_path", :unique=>true, :using=>:btree})
+ -> 0.0028s
+-- add_index("routes", ["path"], {:name=>"index_routes_on_path_text_pattern_ops", :using=>:btree, :opclasses=>{"path"=>"varchar_pattern_ops"}})
+ -> 0.0026s
+-- add_index("routes", ["source_type", "source_id"], {:name=>"index_routes_on_source_type_and_source_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("sent_notifications", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("sent_notifications", ["reply_key"], {:name=>"index_sent_notifications_on_reply_key", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("services", {:force=>:cascade})
+ -> 0.0091s
+-- add_index("services", ["project_id"], {:name=>"index_services_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("services", ["template"], {:name=>"index_services_on_template", :using=>:btree})
+ -> 0.0031s
+-- create_table("snippets", {:force=>:cascade})
+ -> 0.0050s
+-- add_index("snippets", ["author_id"], {:name=>"index_snippets_on_author_id", :using=>:btree})
+ -> 0.0030s
+-- add_index("snippets", ["file_name"], {:name=>"index_snippets_on_file_name_trigram", :using=>:gin, :opclasses=>{"file_name"=>"gin_trgm_ops"}})
+ -> 0.0020s
+-- add_index("snippets", ["project_id"], {:name=>"index_snippets_on_project_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("snippets", ["title"], {:name=>"index_snippets_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}})
+ -> 0.0020s
+-- add_index("snippets", ["updated_at"], {:name=>"index_snippets_on_updated_at", :using=>:btree})
+ -> 0.0026s
+-- add_index("snippets", ["visibility_level"], {:name=>"index_snippets_on_visibility_level", :using=>:btree})
+ -> 0.0026s
+-- create_table("spam_logs", {:force=>:cascade})
+ -> 0.0048s
+-- create_table("subscriptions", {:force=>:cascade})
+ -> 0.0041s
+-- add_index("subscriptions", ["subscribable_id", "subscribable_type", "user_id", "project_id"], {:name=>"index_subscriptions_on_subscribable_and_user_id_and_project_id", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- create_table("system_note_metadata", {:force=>:cascade})
+ -> 0.0040s
+-- add_index("system_note_metadata", ["note_id"], {:name=>"index_system_note_metadata_on_note_id", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- create_table("taggings", {:force=>:cascade})
+ -> 0.0047s
+-- add_index("taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], {:name=>"taggings_idx", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("taggings", ["taggable_id", "taggable_type", "context"], {:name=>"index_taggings_on_taggable_id_and_taggable_type_and_context", :using=>:btree})
+ -> 0.0025s
+-- create_table("tags", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("tags", ["name"], {:name=>"index_tags_on_name", :unique=>true, :using=>:btree})
+ -> 0.0026s
+-- create_table("timelogs", {:force=>:cascade})
+ -> 0.0033s
+-- add_index("timelogs", ["issue_id"], {:name=>"index_timelogs_on_issue_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("timelogs", ["merge_request_id"], {:name=>"index_timelogs_on_merge_request_id", :using=>:btree})
+ -> 0.0033s
+-- add_index("timelogs", ["user_id"], {:name=>"index_timelogs_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("todos", {:force=>:cascade})
+ -> 0.0043s
+-- add_index("todos", ["author_id"], {:name=>"index_todos_on_author_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("todos", ["commit_id"], {:name=>"index_todos_on_commit_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("todos", ["note_id"], {:name=>"index_todos_on_note_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("todos", ["project_id"], {:name=>"index_todos_on_project_id", :using=>:btree})
+ -> 0.0027s
+-- add_index("todos", ["target_type", "target_id"], {:name=>"index_todos_on_target_type_and_target_id", :using=>:btree})
+ -> 0.0028s
+-- add_index("todos", ["user_id"], {:name=>"index_todos_on_user_id", :using=>:btree})
+ -> 0.0026s
+-- create_table("trending_projects", {:force=>:cascade})
+ -> 0.0030s
+-- add_index("trending_projects", ["project_id"], {:name=>"index_trending_projects_on_project_id", :using=>:btree})
+ -> 0.0027s
+-- create_table("u2f_registrations", {:force=>:cascade})
+ -> 0.0048s
+-- add_index("u2f_registrations", ["key_handle"], {:name=>"index_u2f_registrations_on_key_handle", :using=>:btree})
+ -> 0.0029s
+-- add_index("u2f_registrations", ["user_id"], {:name=>"index_u2f_registrations_on_user_id", :using=>:btree})
+ -> 0.0028s
+-- create_table("uploads", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("uploads", ["checksum"], {:name=>"index_uploads_on_checksum", :using=>:btree})
+ -> 0.0028s
+-- add_index("uploads", ["model_id", "model_type"], {:name=>"index_uploads_on_model_id_and_model_type", :using=>:btree})
+ -> 0.0027s
+-- add_index("uploads", ["path"], {:name=>"index_uploads_on_path", :using=>:btree})
+ -> 0.0028s
+-- create_table("user_agent_details", {:force=>:cascade})
+ -> 0.0051s
+-- add_index("user_agent_details", ["subject_id", "subject_type"], {:name=>"index_user_agent_details_on_subject_id_and_subject_type", :using=>:btree})
+ -> 0.0028s
+-- create_table("user_custom_attributes", {:force=>:cascade})
+ -> 0.0044s
+-- add_index("user_custom_attributes", ["key", "value"], {:name=>"index_user_custom_attributes_on_key_and_value", :using=>:btree})
+ -> 0.0027s
+-- add_index("user_custom_attributes", ["user_id", "key"], {:name=>"index_user_custom_attributes_on_user_id_and_key", :unique=>true, :using=>:btree})
+ -> 0.0026s
+-- create_table("user_synced_attributes_metadata", {:force=>:cascade})
+ -> 0.0056s
+-- add_index("user_synced_attributes_metadata", ["user_id"], {:name=>"index_user_synced_attributes_metadata_on_user_id", :unique=>true, :using=>:btree})
+ -> 0.0027s
+-- create_table("users", {:force=>:cascade})
+ -> 0.0134s
+-- add_index("users", ["admin"], {:name=>"index_users_on_admin", :using=>:btree})
+ -> 0.0030s
+-- add_index("users", ["confirmation_token"], {:name=>"index_users_on_confirmation_token", :unique=>true, :using=>:btree})
+ -> 0.0029s
+-- add_index("users", ["created_at"], {:name=>"index_users_on_created_at", :using=>:btree})
+ -> 0.0034s
+-- add_index("users", ["email"], {:name=>"index_users_on_email", :unique=>true, :using=>:btree})
+ -> 0.0030s
+-- add_index("users", ["email"], {:name=>"index_users_on_email_trigram", :using=>:gin, :opclasses=>{"email"=>"gin_trgm_ops"}})
+ -> 0.0431s
+-- add_index("users", ["ghost"], {:name=>"index_users_on_ghost", :using=>:btree})
+ -> 0.0051s
+-- add_index("users", ["incoming_email_token"], {:name=>"index_users_on_incoming_email_token", :using=>:btree})
+ -> 0.0044s
+-- add_index("users", ["name"], {:name=>"index_users_on_name", :using=>:btree})
+ -> 0.0044s
+-- add_index("users", ["name"], {:name=>"index_users_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}})
+ -> 0.0034s
+-- add_index("users", ["reset_password_token"], {:name=>"index_users_on_reset_password_token", :unique=>true, :using=>:btree})
+ -> 0.0044s
+-- add_index("users", ["rss_token"], {:name=>"index_users_on_rss_token", :using=>:btree})
+ -> 0.0046s
+-- add_index("users", ["state"], {:name=>"index_users_on_state", :using=>:btree})
+ -> 0.0040s
+-- add_index("users", ["username"], {:name=>"index_users_on_username", :using=>:btree})
+ -> 0.0046s
+-- add_index("users", ["username"], {:name=>"index_users_on_username_trigram", :using=>:gin, :opclasses=>{"username"=>"gin_trgm_ops"}})
+ -> 0.0044s
+-- create_table("users_star_projects", {:force=>:cascade})
+ -> 0.0055s
+-- add_index("users_star_projects", ["project_id"], {:name=>"index_users_star_projects_on_project_id", :using=>:btree})
+ -> 0.0037s
+-- add_index("users_star_projects", ["user_id", "project_id"], {:name=>"index_users_star_projects_on_user_id_and_project_id", :unique=>true, :using=>:btree})
+ -> 0.0044s
+-- create_table("web_hook_logs", {:force=>:cascade})
+ -> 0.0060s
+-- add_index("web_hook_logs", ["web_hook_id"], {:name=>"index_web_hook_logs_on_web_hook_id", :using=>:btree})
+ -> 0.0034s
+-- create_table("web_hooks", {:force=>:cascade})
+ -> 0.0120s
+-- add_index("web_hooks", ["project_id"], {:name=>"index_web_hooks_on_project_id", :using=>:btree})
+ -> 0.0038s
+-- add_index("web_hooks", ["type"], {:name=>"index_web_hooks_on_type", :using=>:btree})
+ -> 0.0036s
+-- add_foreign_key("boards", "projects", {:name=>"fk_f15266b5f9", :on_delete=>:cascade})
+ -> 0.0030s
+-- add_foreign_key("chat_teams", "namespaces", {:on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("ci_build_trace_section_names", "projects", {:on_delete=>:cascade})
+ -> 0.0022s
+-- add_foreign_key("ci_build_trace_sections", "ci_build_trace_section_names", {:column=>"section_name_id", :name=>"fk_264e112c66", :on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("ci_build_trace_sections", "ci_builds", {:column=>"build_id", :name=>"fk_4ebe41f502", :on_delete=>:cascade})
+ -> 0.0024s
+-- add_foreign_key("ci_build_trace_sections", "projects", {:on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("ci_builds", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_a2141b1522", :on_delete=>:nullify})
+ -> 0.0023s
+-- add_foreign_key("ci_builds", "ci_stages", {:column=>"stage_id", :name=>"fk_3a9eaa254d", :on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("ci_builds", "projects", {:name=>"fk_befce0568a", :on_delete=>:cascade})
+ -> 0.0024s
+-- add_foreign_key("ci_group_variables", "namespaces", {:column=>"group_id", :name=>"fk_33ae4d58d8", :on_delete=>:cascade})
+ -> 0.0024s
+-- add_foreign_key("ci_job_artifacts", "ci_builds", {:column=>"job_id", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("ci_job_artifacts", "projects", {:on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("ci_pipeline_schedule_variables", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_41c35fda51", :on_delete=>:cascade})
+ -> 0.0027s
+-- add_foreign_key("ci_pipeline_schedules", "projects", {:name=>"fk_8ead60fcc4", :on_delete=>:cascade})
+ -> 0.0022s
+-- add_foreign_key("ci_pipeline_schedules", "users", {:column=>"owner_id", :name=>"fk_9ea99f58d2", :on_delete=>:nullify})
+ -> 0.0025s
+-- add_foreign_key("ci_pipeline_variables", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_f29c5f4380", :on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("ci_pipelines", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_3d34ab2e06", :on_delete=>:nullify})
+ -> 0.0019s
+-- add_foreign_key("ci_pipelines", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_262d4c2d19", :on_delete=>:nullify})
+ -> 0.0029s
+-- add_foreign_key("ci_pipelines", "projects", {:name=>"fk_86635dbd80", :on_delete=>:cascade})
+ -> 0.0023s
+-- add_foreign_key("ci_runner_projects", "projects", {:name=>"fk_4478a6f1e4", :on_delete=>:cascade})
+ -> 0.0036s
+-- add_foreign_key("ci_stages", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_fb57e6cc56", :on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("ci_stages", "projects", {:name=>"fk_2360681d1d", :on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("ci_trigger_requests", "ci_triggers", {:column=>"trigger_id", :name=>"fk_b8ec8b7245", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("ci_triggers", "projects", {:name=>"fk_e3e63f966e", :on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("ci_triggers", "users", {:column=>"owner_id", :name=>"fk_e8e10d1964", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("ci_variables", "projects", {:name=>"fk_ada5eb64b3", :on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("cluster_platforms_kubernetes", "clusters", {:on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("cluster_projects", "clusters", {:on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("cluster_projects", "projects", {:on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("cluster_providers_gcp", "clusters", {:on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("clusters", "users", {:on_delete=>:nullify})
+ -> 0.0018s
+-- add_foreign_key("clusters_applications_helm", "clusters", {:on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("container_repositories", "projects")
+ -> 0.0020s
+-- add_foreign_key("deploy_keys_projects", "projects", {:name=>"fk_58a901ca7e", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("deployments", "projects", {:name=>"fk_b9a3851b82", :on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("environments", "projects", {:name=>"fk_d1c8c1da6a", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("events", "projects", {:on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("events", "users", {:column=>"author_id", :name=>"fk_edfd187b6f", :on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("fork_network_members", "fork_networks", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("fork_network_members", "projects", {:column=>"forked_from_project_id", :name=>"fk_b01280dae4", :on_delete=>:nullify})
+ -> 0.0019s
+-- add_foreign_key("fork_network_members", "projects", {:on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("fork_networks", "projects", {:column=>"root_project_id", :name=>"fk_e7b436b2b5", :on_delete=>:nullify})
+ -> 0.0018s
+-- add_foreign_key("forked_project_links", "projects", {:column=>"forked_to_project_id", :name=>"fk_434510edb0", :on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("gcp_clusters", "projects", {:on_delete=>:cascade})
+ -> 0.0029s
+-- add_foreign_key("gcp_clusters", "services", {:on_delete=>:nullify})
+ -> 0.0022s
+-- add_foreign_key("gcp_clusters", "users", {:on_delete=>:nullify})
+ -> 0.0019s
+-- add_foreign_key("gpg_key_subkeys", "gpg_keys", {:on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("gpg_keys", "users", {:on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("gpg_signatures", "gpg_key_subkeys", {:on_delete=>:nullify})
+ -> 0.0016s
+-- add_foreign_key("gpg_signatures", "gpg_keys", {:on_delete=>:nullify})
+ -> 0.0016s
+-- add_foreign_key("gpg_signatures", "projects", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("group_custom_attributes", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("issue_assignees", "issues", {:name=>"fk_b7d881734a", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("issue_assignees", "users", {:name=>"fk_5e0c8d9154", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("issue_metrics", "issues", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("issues", "issues", {:column=>"moved_to_id", :name=>"fk_a194299be1", :on_delete=>:nullify})
+ -> 0.0014s
+-- add_foreign_key("issues", "milestones", {:name=>"fk_96b1dd429c", :on_delete=>:nullify})
+ -> 0.0016s
+-- add_foreign_key("issues", "projects", {:name=>"fk_899c8f3231", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("issues", "users", {:column=>"author_id", :name=>"fk_05f1e72feb", :on_delete=>:nullify})
+ -> 0.0015s
+-- add_foreign_key("issues", "users", {:column=>"updated_by_id", :name=>"fk_ffed080f01", :on_delete=>:nullify})
+ -> 0.0017s
+-- add_foreign_key("label_priorities", "labels", {:on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("label_priorities", "projects", {:on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("labels", "namespaces", {:column=>"group_id", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("labels", "projects", {:name=>"fk_7de4989a69", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("lists", "boards", {:name=>"fk_0d3f677137", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("lists", "labels", {:name=>"fk_7a5553d60f", :on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("members", "users", {:name=>"fk_2e88fb7ce9", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("merge_request_diff_commits", "merge_request_diffs", {:on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("merge_request_diff_files", "merge_request_diffs", {:on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("merge_request_diffs", "merge_requests", {:name=>"fk_8483f3258f", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("merge_request_metrics", "ci_pipelines", {:column=>"pipeline_id", :on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("merge_request_metrics", "merge_requests", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("merge_request_metrics", "users", {:column=>"latest_closed_by_id", :name=>"fk_ae440388cc", :on_delete=>:nullify})
+ -> 0.0015s
+-- add_foreign_key("merge_request_metrics", "users", {:column=>"merged_by_id", :name=>"fk_7f28d925f3", :on_delete=>:nullify})
+ -> 0.0015s
+-- add_foreign_key("merge_requests", "ci_pipelines", {:column=>"head_pipeline_id", :name=>"fk_fd82eae0b9", :on_delete=>:nullify})
+ -> 0.0014s
+-- add_foreign_key("merge_requests", "merge_request_diffs", {:column=>"latest_merge_request_diff_id", :name=>"fk_06067f5644", :on_delete=>:nullify})
+ -> 0.0014s
+-- add_foreign_key("merge_requests", "milestones", {:name=>"fk_6a5165a692", :on_delete=>:nullify})
+ -> 0.0015s
+-- add_foreign_key("merge_requests", "projects", {:column=>"source_project_id", :name=>"fk_3308fe130c", :on_delete=>:nullify})
+ -> 0.0017s
+-- add_foreign_key("merge_requests", "projects", {:column=>"target_project_id", :name=>"fk_a6963e8447", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("merge_requests", "users", {:column=>"assignee_id", :name=>"fk_6149611a04", :on_delete=>:nullify})
+ -> 0.0016s
+-- add_foreign_key("merge_requests", "users", {:column=>"author_id", :name=>"fk_e719a85f8a", :on_delete=>:nullify})
+ -> 0.0017s
+-- add_foreign_key("merge_requests", "users", {:column=>"merge_user_id", :name=>"fk_ad525e1f87", :on_delete=>:nullify})
+ -> 0.0018s
+-- add_foreign_key("merge_requests", "users", {:column=>"updated_by_id", :name=>"fk_641731faff", :on_delete=>:nullify})
+ -> 0.0017s
+-- add_foreign_key("merge_requests_closing_issues", "issues", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("merge_requests_closing_issues", "merge_requests", {:on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("milestones", "namespaces", {:column=>"group_id", :name=>"fk_95650a40d4", :on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("milestones", "projects", {:name=>"fk_9bd0a0c791", :on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("notes", "projects", {:name=>"fk_99e097b079", :on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("oauth_openid_requests", "oauth_access_grants", {:column=>"access_grant_id", :name=>"fk_oauth_openid_requests_oauth_access_grants_access_grant_id"})
+ -> 0.0014s
+-- add_foreign_key("pages_domains", "projects", {:name=>"fk_ea2f6dfc6f", :on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("personal_access_tokens", "users")
+ -> 0.0016s
+-- add_foreign_key("project_authorizations", "projects", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("project_authorizations", "users", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("project_auto_devops", "projects", {:on_delete=>:cascade})
+ -> 0.0026s
+-- add_foreign_key("project_custom_attributes", "projects", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("project_features", "projects", {:name=>"fk_18513d9b92", :on_delete=>:cascade})
+ -> 0.0020s
+-- add_foreign_key("project_group_links", "projects", {:name=>"fk_daa8cee94c", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("project_import_data", "projects", {:name=>"fk_ffb9ee3a10", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("project_statistics", "projects", {:on_delete=>:cascade})
+ -> 0.0021s
+-- add_foreign_key("protected_branch_merge_access_levels", "protected_branches", {:name=>"fk_8a3072ccb3", :on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("protected_branch_push_access_levels", "protected_branches", {:name=>"fk_9ffc86a3d9", :on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("protected_branches", "projects", {:name=>"fk_7a9c6d93e7", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("protected_tag_create_access_levels", "namespaces", {:column=>"group_id"})
+ -> 0.0016s
+-- add_foreign_key("protected_tag_create_access_levels", "protected_tags", {:name=>"fk_f7dfda8c51", :on_delete=>:cascade})
+ -> 0.0013s
+-- add_foreign_key("protected_tag_create_access_levels", "users")
+ -> 0.0018s
+-- add_foreign_key("protected_tags", "projects", {:name=>"fk_8e4af87648", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("push_event_payloads", "events", {:name=>"fk_36c74129da", :on_delete=>:cascade})
+ -> 0.0013s
+-- add_foreign_key("releases", "projects", {:name=>"fk_47fe2a0596", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("services", "projects", {:name=>"fk_71cce407f9", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("snippets", "projects", {:name=>"fk_be41fd4bb7", :on_delete=>:cascade})
+ -> 0.0017s
+-- add_foreign_key("subscriptions", "projects", {:on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("system_note_metadata", "notes", {:name=>"fk_d83a918cb1", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("timelogs", "issues", {:name=>"fk_timelogs_issues_issue_id", :on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("timelogs", "merge_requests", {:name=>"fk_timelogs_merge_requests_merge_request_id", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("todos", "projects", {:name=>"fk_45054f9c45", :on_delete=>:cascade})
+ -> 0.0018s
+-- add_foreign_key("trending_projects", "projects", {:on_delete=>:cascade})
+ -> 0.0015s
+-- add_foreign_key("u2f_registrations", "users")
+ -> 0.0017s
+-- add_foreign_key("user_custom_attributes", "users", {:on_delete=>:cascade})
+ -> 0.0019s
+-- add_foreign_key("user_synced_attributes_metadata", "users", {:on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("users_star_projects", "projects", {:name=>"fk_22cd27ddfc", :on_delete=>:cascade})
+ -> 0.0016s
+-- add_foreign_key("web_hook_logs", "web_hooks", {:on_delete=>:cascade})
+ -> 0.0014s
+-- add_foreign_key("web_hooks", "projects", {:name=>"fk_0c8ca6d9d1", :on_delete=>:cascade})
+ -> 0.0017s
+-- initialize_schema_migrations_table()
+ -> 0.0112s
+$ JOB_NAME=( $CI_JOB_NAME )
+$ export CI_NODE_INDEX=${JOB_NAME[-2]}
+$ export CI_NODE_TOTAL=${JOB_NAME[-1]}
+$ export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+$ export KNAPSACK_GENERATE_REPORT=true
+$ export CACHE_CLASSES=true
+$ cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
+$ scripts/gitaly-test-spawn
+Gem.path: ["/root/.gem/ruby/2.3.0", "/usr/local/lib/ruby/gems/2.3.0", "/usr/local/bundle"]
+ENV['BUNDLE_GEMFILE']: nil
+ENV['RUBYOPT']: nil
+bundle config in /builds/gitlab-org/gitlab-ce
+scripts/gitaly-test-spawn:10:in `<main>': undefined local variable or method `gitaly_dir' for main:Object (NameError)
+Did you mean? gitaly_dir
+Settings are listed in order of priority. The top value will be used.
+retry
+Set for your local app (/usr/local/bundle/config): 3
+
+path
+Set for your local app (/usr/local/bundle/config): "vendor"
+Set via BUNDLE_PATH: "/usr/local/bundle"
+
+jobs
+Set for your local app (/usr/local/bundle/config): "2"
+
+clean
+Set for your local app (/usr/local/bundle/config): "true"
+
+without
+Set for your local app (/usr/local/bundle/config): [:production]
+
+silence_root_warning
+Set via BUNDLE_SILENCE_ROOT_WARNING: true
+
+app_config
+Set via BUNDLE_APP_CONFIG: "/usr/local/bundle"
+
+install_flags
+Set via BUNDLE_INSTALL_FLAGS: "--without=production --jobs=2 --path=vendor --retry=3 --quiet"
+
+bin
+Set via BUNDLE_BIN: "/usr/local/bundle/bin"
+
+gemfile
+Set via BUNDLE_GEMFILE: "/builds/gitlab-org/gitlab-ce/Gemfile"
+
+section_end:1517486961:build_script
+section_start:1517486961:after_script
+section_end:1517486962:after_script
+section_start:1517486962:upload_artifacts
+Uploading artifacts...
+WARNING: coverage/: no matching files 
+knapsack/: found 5 matching files 
+WARNING: tmp/capybara/: no matching files 
+Uploading artifacts to coordinator... ok  id=50551722 responseStatus=201 Created token=XkN753rp
+section_end:1517486963:upload_artifacts
+ERROR: Job failed: exit code 1
+ \ No newline at end of file
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index 7b38f6b7855..a9e244e523d 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -71,7 +71,8 @@ describe('Clusters', () => {
helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' },
});
- expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeNull();
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+ expect(flashMessage).toBeNull();
});
it('shows an alert when something gets newly installed', () => {
@@ -83,8 +84,9 @@ describe('Clusters', () => {
helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
});
- expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined();
- expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller was successfully installed on your cluster');
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+ expect(flashMessage).not.toBeNull();
+ expect(flashMessage.textContent.trim()).toEqual('Helm Tiller was successfully installed on your Kubernetes cluster');
});
it('shows an alert when multiple things gets newly installed', () => {
@@ -98,8 +100,9 @@ describe('Clusters', () => {
ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' },
});
- expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined();
- expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your cluster');
+ const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
+ expect(flashMessage).not.toBeNull();
+ expect(flashMessage.textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your Kubernetes cluster');
});
});
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index ec2889355e6..726a4ed30de 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -58,6 +58,7 @@ describe('Clusters Store', () => {
expect(store.state).toEqual({
helpPath: null,
+ ingressHelpPath: null,
status: mockResponseData.status,
statusReason: mockResponseData.status_reason,
applications: {
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index 87d131dfe28..6d5c6d5334f 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -7,7 +7,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
+ let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index 4cdb679c97f..2b69e718e08 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
end
after do
- [MergeRequest, MergeRequestDiff].each(&:reset_column_information)
+ [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information)
end
describe '#perform' do
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 633e319f46d..a65012d2314 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -2,18 +2,20 @@ require 'spec_helper'
describe Gitlab::Checks::ForcePush do
let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
context "exit code checking", :skip_gitaly_mock do
it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0])
+ allow(repository).to receive(:popen).and_return(['normal output', 0])
expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
end
- it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['error', 1])
+ it "raises a GitError error if the `popen` call to git returns a non-zero exit code" do
+ allow(repository).to receive(:popen).and_return(['error', 1])
- expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError)
+ expect { described_class.force_push?(project, 'oldrev', 'newrev') }
+ .to raise_error(Gitlab::Git::Repository::GitError)
end
end
end
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
new file mode 100644
index 00000000000..ac02007e111
--- /dev/null
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ describe '.fetch_message' do
+ context 'with a project created message queue' do
+ let(:project_created) { described_class.new(project, user, 'http') }
+
+ before do
+ project_created.add_message
+ end
+
+ it 'returns project created message' do
+ expect(described_class.fetch_message(user.id, project.id)).to eq(project_created.message)
+ end
+
+ it 'deletes the project created message from redis' do
+ expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).not_to be_nil
+ described_class.fetch_message(user.id, project.id)
+ expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).to be_nil
+ end
+ end
+
+ context 'with no project created message queue' do
+ it 'returns nil' do
+ expect(described_class.fetch_message(1, 2)).to be_nil
+ end
+ end
+ end
+
+ describe '#add_message' do
+ it 'queues a project created message' do
+ project_created = described_class.new(project, user, 'http')
+
+ expect(project_created.add_message).to eq('OK')
+ end
+
+ it 'handles anonymous push' do
+ project_created = described_class.new(nil, user, 'http')
+
+ expect(project_created.add_message).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index f90c2d6aded..e263d29656c 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -4,82 +4,82 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
let(:project) { create(:project) }
- describe '.fetch_redirct_message' do
+ describe '.fetch_message' do
context 'with a redirect message queue' do
- it 'should return the redirect message' do
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
- project_moved.add_redirect_message
+ it 'returns the redirect message' do
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
+ project_moved.add_message
- expect(described_class.fetch_redirect_message(user.id, project.id)).to eq(project_moved.redirect_message)
+ expect(described_class.fetch_message(user.id, project.id)).to eq(project_moved.message)
end
- it 'should delete the redirect message from redis' do
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
- project_moved.add_redirect_message
+ it 'deletes the redirect message from redis' do
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
+ project_moved.add_message
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).not_to be_nil
- described_class.fetch_redirect_message(user.id, project.id)
+ described_class.fetch_message(user.id, project.id)
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).to be_nil
end
end
context 'with no redirect message queue' do
- it 'should return nil' do
- expect(described_class.fetch_redirect_message(1, 2)).to be_nil
+ it 'returns nil' do
+ expect(described_class.fetch_message(1, 2)).to be_nil
end
end
end
- describe '#add_redirect_message' do
- it 'should queue a redirect message' do
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
- expect(project_moved.add_redirect_message).to eq("OK")
+ describe '#add_message' do
+ it 'queues a redirect message' do
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
+ expect(project_moved.add_message).to eq("OK")
end
- it 'should handle anonymous clones' do
- project_moved = described_class.new(project, nil, 'foo/bar', 'http')
+ it 'handles anonymous clones' do
+ project_moved = described_class.new(project, nil, 'http', 'foo/bar')
- expect(project_moved.add_redirect_message).to eq(nil)
+ expect(project_moved.add_message).to eq(nil)
end
end
- describe '#redirect_message' do
+ describe '#message' do
context 'when the push is rejected' do
- it 'should return a redirect message telling the user to try again' do
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
+ it 'returns a redirect message telling the user to try again' do
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
message = "Project 'foo/bar' was moved to '#{project.full_path}'." +
"\n\nPlease update your Git remote:" +
"\n\n git remote set-url origin #{project.http_url_to_repo} and try again.\n"
- expect(project_moved.redirect_message(rejected: true)).to eq(message)
+ expect(project_moved.message(rejected: true)).to eq(message)
end
end
context 'when the push is not rejected' do
- it 'should return a redirect message' do
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
+ it 'returns a redirect message' do
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
message = "Project 'foo/bar' was moved to '#{project.full_path}'." +
"\n\nPlease update your Git remote:" +
"\n\n git remote set-url origin #{project.http_url_to_repo}\n"
- expect(project_moved.redirect_message).to eq(message)
+ expect(project_moved.message).to eq(message)
end
end
end
describe '#permanent_redirect?' do
context 'with a permanent RedirectRoute' do
- it 'should return true' do
+ it 'returns true' do
project.route.create_redirect('foo/bar', permanent: true)
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
expect(project_moved.permanent_redirect?).to be_truthy
end
end
context 'without a permanent RedirectRoute' do
- it 'should return false' do
+ it 'returns false' do
project.route.create_redirect('foo/bar')
- project_moved = described_class.new(project, user, 'foo/bar', 'http')
+ project_moved = described_class.new(project, user, 'http', 'foo/bar')
expect(project_moved.permanent_redirect?).to be_falsy
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 3546532b9b4..91c9625ba06 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -238,11 +238,98 @@ describe Gitlab::Ci::Trace do
end
end
+ describe '#read' do
+ shared_examples 'read successfully with IO' do
+ it 'yields with source' do
+ trace.read do |stream|
+ expect(stream).to be_a(Gitlab::Ci::Trace::Stream)
+ expect(stream.stream).to be_a(IO)
+ end
+ end
+ end
+
+ shared_examples 'read successfully with StringIO' do
+ it 'yields with source' do
+ trace.read do |stream|
+ expect(stream).to be_a(Gitlab::Ci::Trace::Stream)
+ expect(stream.stream).to be_a(StringIO)
+ end
+ end
+ end
+
+ shared_examples 'failed to read' do
+ it 'yields without source' do
+ trace.read do |stream|
+ expect(stream).to be_a(Gitlab::Ci::Trace::Stream)
+ expect(stream.stream).to be_nil
+ end
+ end
+ end
+
+ context 'when trace artifact exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it_behaves_like 'read successfully with IO'
+ end
+
+ context 'when current_path (with project_id) exists' do
+ before do
+ expect(trace).to receive(:default_path) { expand_fixture_path('trace/sample_trace') }
+ end
+
+ it_behaves_like 'read successfully with IO'
+ end
+
+ context 'when current_path (with project_ci_id) exists' do
+ before do
+ expect(trace).to receive(:deprecated_path) { expand_fixture_path('trace/sample_trace') }
+ end
+
+ it_behaves_like 'read successfully with IO'
+ end
+
+ context 'when db trace exists' do
+ before do
+ build.send(:write_attribute, :trace, "data")
+ end
+
+ it_behaves_like 'read successfully with StringIO'
+ end
+
+ context 'when no sources exist' do
+ it_behaves_like 'failed to read'
+ end
+ end
+
describe 'trace handling' do
+ subject { trace.exist? }
+
context 'trace does not exist' do
it { expect(trace.exist?).to be(false) }
end
+ context 'when trace artifact exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the trace artifact has been erased' do
+ before do
+ trace.erase!
+ end
+
+ it { is_expected.to be_falsy }
+
+ it 'removes associations' do
+ expect(Ci::JobArtifact.exists?(job_id: build.id, file_type: :trace)).to be_falsy
+ end
+ end
+ end
+
context 'new trace path is used' do
before do
trace.send(:ensure_directory)
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 8c79ef54c6c..28c679af12a 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:project2) { create(:project) }
- let(:forked_project) { Projects::ForkService.new(project, project.creator).execute }
+ let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute }
let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
@@ -14,6 +14,7 @@ describe Gitlab::ClosingIssueExtractor do
before do
project.add_developer(project.creator)
+ project.add_developer(project2.creator)
project2.add_master(project.creator)
end
diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
new file mode 100644
index 00000000000..d7f76737f3f
--- /dev/null
+++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Git::LfsPointerFile do
+ let(:data) { "1234\n" }
+
+ subject { described_class.new(data) }
+
+ describe '#size' do
+ it 'counts the bytes' do
+ expect(subject.size).to eq 5
+ end
+
+ it 'handles non ascii data' do
+ expect(described_class.new("ääää").size).to eq 8
+ end
+ end
+
+ describe '#sha256' do
+ it 'hashes the content correctly' do
+ expect(subject.sha256).to eq 'a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4'
+ end
+ end
+
+ describe '#pointer' do
+ it 'starts with the LFS version' do
+ expect(subject.pointer).to start_with('version https://git-lfs.github.com/spec/v1')
+ end
+
+ it 'includes sha256' do
+ expect(subject.pointer).to match(/^oid sha256:[0-9a-fA-F]{64}/)
+ end
+
+ it 'ends with the size' do
+ expect(subject.pointer).to end_with("\nsize 5\n")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 90fbef9d248..4e0ee206219 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -1,51 +1,42 @@
require 'spec_helper'
describe Gitlab::Git::RevList do
- let(:project) { create(:project, :repository) }
- let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+ let(:repository) { create(:project, :repository).repository.raw }
+ let(:rev_list) { described_class.new(repository, newrev: 'newrev') }
let(:env_hash) do
{
'GIT_OBJECT_DIRECTORY' => 'foo',
'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar'
}
end
+ let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } }
before do
- allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash.symbolize_keys)
+ allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash)
end
def args_for_popen(args_list)
- [
- Gitlab.config.git.bin_path,
- "--git-dir=#{project.repository.path_to_repo}",
- 'rev-list',
- *args_list
- ]
- end
-
- def stub_popen_rev_list(*additional_args, output:)
- args = args_for_popen(additional_args)
-
- expect(rev_list).to receive(:popen).with(args, nil, env_hash)
- .and_return([output, 0])
+ [Gitlab.config.git.bin_path, 'rev-list', *args_list]
end
- def stub_lazy_popen_rev_list(*additional_args, output:)
+ def stub_popen_rev_list(*additional_args, with_lazy_block: true, output:)
params = [
args_for_popen(additional_args),
- nil,
- env_hash,
- hash_including(lazy_block: anything)
+ repository.path,
+ command_env,
+ hash_including(lazy_block: with_lazy_block ? anything : nil)
]
- expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:|
- lazy_block.call(output.lines.lazy.map(&:chomp))
+ expect(repository).to receive(:popen).with(*params) do |*_, lazy_block:|
+ output = lazy_block.call(output.lines.lazy.map(&:chomp)) if with_lazy_block
+
+ [output, 0]
end
end
context "#new_refs" do
it 'calls out to `popen`' do
- stub_popen_rev_list('newrev', '--not', '--all', output: "sha1\nsha2")
+ stub_popen_rev_list('newrev', '--not', '--all', with_lazy_block: false, output: "sha1\nsha2")
expect(rev_list.new_refs).to eq(%w[sha1 sha2])
end
@@ -55,18 +46,18 @@ describe Gitlab::Git::RevList do
it 'fetches list of newly pushed objects using rev-list' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
- expect(rev_list.new_objects).to eq(%w[sha1 sha2])
+ expect { |b| rev_list.new_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end
it 'can skip pathless objects' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file")
- expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2])
+ expect { |b| rev_list.new_objects(require_path: true, &b) }.to yield_with_args(%w[sha2])
end
it 'can handle non utf-8 paths' do
non_utf_char = [0x89].pack("c*").force_encoding("UTF-8")
- stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
+ stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
rev_list.new_objects(require_path: true) do |object_ids|
expect(object_ids.force).to eq(%w[sha2])
@@ -74,7 +65,7 @@ describe Gitlab::Git::RevList do
end
it 'can yield a lazy enumerator' do
- stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
+ stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
rev_list.new_objects do |object_ids|
expect(object_ids).to be_a Enumerator::Lazy
@@ -82,7 +73,7 @@ describe Gitlab::Git::RevList do
end
it 'returns the result of the block when given' do
- stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
+ stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
objects = rev_list.new_objects do |object_ids|
object_ids.first
@@ -94,13 +85,13 @@ describe Gitlab::Git::RevList do
it 'can accept list of references to exclude' do
stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2")
- expect(rev_list.new_objects(not_in: ['master'])).to eq(%w[sha1 sha2])
+ expect { |b| rev_list.new_objects(not_in: ['master'], &b) }.to yield_with_args(%w[sha1 sha2])
end
it 'handles empty list of references to exclude as listing all known objects' do
stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2")
- expect(rev_list.new_objects(not_in: [])).to eq(%w[sha1 sha2])
+ expect { |b| rev_list.new_objects(not_in: [], &b) }.to yield_with_args(%w[sha1 sha2])
end
end
@@ -108,15 +99,15 @@ describe Gitlab::Git::RevList do
it 'fetches list of all pushed objects using rev-list' do
stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2")
- expect(rev_list.all_objects).to eq(%w[sha1 sha2])
+ expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end
end
context "#missed_ref" do
- let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+ let(:rev_list) { described_class.new(repository, oldrev: 'oldrev', newrev: 'newrev') }
it 'calls out to `popen`' do
- stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', output: "sha1\nsha2")
+ stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', with_lazy_block: false, output: "sha1\nsha2")
expect(rev_list.missed_ref).to eq(%w[sha1 sha2])
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 2009a8ac48c..3c3697e7aa9 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -5,11 +5,19 @@ describe Gitlab::GitAccess do
let(:actor) { user }
let(:project) { create(:project, :repository) }
+ let(:project_path) { project.path }
+ let(:namespace_path) { project&.namespace&.path }
let(:protocol) { 'ssh' }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
- let(:access) { described_class.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
+ let(:access) do
+ described_class.new(actor, project,
+ protocol, authentication_abilities: authentication_abilities,
+ namespace_path: namespace_path, project_path: project_path,
+ redirected_path: redirected_path)
+ end
+
let(:push_access_check) { access.check('git-receive-pack', '_any') }
let(:pull_access_check) { access.check('git-upload-pack', '_any') }
@@ -111,7 +119,7 @@ describe Gitlab::GitAccess do
end
it 'does not block pushes with "not found"' do
- expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload])
+ expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
end
end
end
@@ -145,6 +153,7 @@ describe Gitlab::GitAccess do
context 'when the project is nil' do
let(:project) { nil }
+ let(:project_path) { "new-project" }
it 'blocks push and pull with "not found"' do
aggregate_failures do
@@ -152,6 +161,42 @@ describe Gitlab::GitAccess do
expect { push_access_check }.to raise_not_found
end
end
+
+ context 'when user is allowed to create project in namespace' do
+ let(:namespace_path) { user.namespace.path }
+ let(:access) do
+ described_class.new(actor, nil,
+ protocol, authentication_abilities: authentication_abilities,
+ project_path: project_path, namespace_path: namespace_path,
+ redirected_path: redirected_path)
+ end
+
+ it 'blocks pull access with "not found"' do
+ expect { pull_access_check }.to raise_not_found
+ end
+
+ it 'allows push access' do
+ expect { push_access_check }.not_to raise_error
+ end
+ end
+
+ context 'when user is not allowed to create project in namespace' do
+ let(:user2) { create(:user) }
+ let(:namespace_path) { user2.namespace.path }
+ let(:access) do
+ described_class.new(actor, nil,
+ protocol, authentication_abilities: authentication_abilities,
+ project_path: project_path, namespace_path: namespace_path,
+ redirected_path: redirected_path)
+ end
+
+ it 'blocks push and pull with "not found"' do
+ aggregate_failures do
+ expect { pull_access_check }.to raise_not_found
+ expect { push_access_check }.to raise_not_found
+ end
+ end
+ end
end
end
@@ -197,7 +242,7 @@ describe Gitlab::GitAccess do
it 'enqueues a redirected message' do
push_access_check
- expect(Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id)).not_to be_nil
+ expect(Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)).not_to be_nil
end
end
@@ -273,6 +318,52 @@ describe Gitlab::GitAccess do
end
end
+ describe '#check_authentication_abilities!' do
+ before do
+ project.add_master(user)
+ end
+
+ context 'when download' do
+ let(:authentication_abilities) { [] }
+
+ it 'raises unauthorized with download error' do
+ expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_download])
+ end
+
+ context 'when authentication abilities include download code' do
+ let(:authentication_abilities) { [:download_code] }
+
+ it 'does not raise any errors' do
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+
+ context 'when authentication abilities include build download code' do
+ let(:authentication_abilities) { [:build_download_code] }
+
+ it 'does not raise any errors' do
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when upload' do
+ let(:authentication_abilities) { [] }
+
+ it 'raises unauthorized with push error' do
+ expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
+ end
+
+ context 'when authentication abilities include push code' do
+ let(:authentication_abilities) { [:push_code] }
+
+ it 'does not raise any errors' do
+ expect { push_access_check }.not_to raise_error
+ end
+ end
+ end
+ end
+
describe '#check_command_disabled!' do
before do
project.add_master(user)
@@ -311,6 +402,117 @@ describe Gitlab::GitAccess do
end
end
+ describe '#check_db_accessibility!' do
+ context 'when in a read-only GitLab instance' do
+ before do
+ create(:protected_branch, name: 'feature', project: project)
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+ end
+
+ it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:cannot_push_to_read_only]) }
+ end
+ end
+
+ describe '#ensure_project_on_push!' do
+ let(:access) do
+ described_class.new(actor, project,
+ protocol, authentication_abilities: authentication_abilities,
+ project_path: project_path, namespace_path: namespace_path,
+ redirected_path: redirected_path)
+ end
+
+ context 'when push' do
+ let(:cmd) { 'git-receive-pack' }
+
+ context 'when project does not exist' do
+ let(:project_path) { "nonexistent" }
+ let(:project) { nil }
+
+ context 'when changes is _any' do
+ let(:changes) { '_any' }
+
+ context 'when authentication abilities include push code' do
+ let(:authentication_abilities) { [:push_code] }
+
+ context 'when user can create project in namespace' do
+ let(:namespace_path) { user.namespace.path }
+
+ it 'creates a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.to change { Project.count }.by(1)
+ end
+ end
+
+ context 'when user cannot create project in namespace' do
+ let(:user2) { create(:user) }
+ let(:namespace_path) { user2.namespace.path }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+ end
+
+ context 'when authentication abilities do not include push code' do
+ let(:authentication_abilities) { [] }
+
+ context 'when user can create project in namespace' do
+ let(:namespace_path) { user.namespace.path }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+ end
+ end
+
+ context 'when check contains actual changes' do
+ let(:changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+ end
+
+ context 'when project exists' do
+ let(:changes) { '_any' }
+ let!(:project) { create(:project) }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+
+ context 'when deploy key is used' do
+ let(:key) { create(:deploy_key, user: user) }
+ let(:actor) { key }
+ let(:project_path) { "nonexistent" }
+ let(:project) { nil }
+ let(:namespace_path) { user.namespace.path }
+ let(:changes) { '_any' }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+ end
+
+ context 'when pull' do
+ let(:cmd) { 'git-upload-pack' }
+ let(:changes) { '_any' }
+
+ context 'when project does not exist' do
+ let(:project_path) { "new-project" }
+ let(:namespace_path) { user.namespace.path }
+ let(:project) { nil }
+
+ it 'does not create a new project' do
+ expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
+ end
+ end
+ end
+ end
+
describe '#check_download_access!' do
it 'allows masters to pull' do
project.add_master(user)
@@ -338,7 +540,9 @@ describe Gitlab::GitAccess do
context 'when project is public' do
let(:public_project) { create(:project, :public, :repository) }
- let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: []) }
+ let(:project_path) { public_project.path }
+ let(:namespace_path) { public_project.namespace.path }
+ let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], project_path: project_path, namespace_path: namespace_path) }
context 'when repository is enabled' do
it 'give access to download code' do
@@ -638,19 +842,6 @@ describe Gitlab::GitAccess do
admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
end
end
-
- context "when in a read-only GitLab instance" do
- before do
- create(:protected_branch, name: 'feature', project: project)
- allow(Gitlab::Database).to receive(:read_only?) { true }
- end
-
- # Only check admin; if an admin can't do it, other roles can't either
- matrix = permissions_matrix[:admin].dup
- matrix.each { |key, _| matrix[key] = false }
-
- run_permission_checks(admin: matrix)
- end
end
describe 'build authentication abilities' do
@@ -661,26 +852,26 @@ describe Gitlab::GitAccess do
project.add_reporter(user)
end
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) }
+ it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) }
+ it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) }
+ it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
- it { expect { push_access_check }.to raise_not_found }
+ it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
end
end
end
@@ -767,8 +958,7 @@ describe Gitlab::GitAccess do
end
def raise_not_found
- raise_error(Gitlab::GitAccess::NotFoundError,
- Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
+ raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
end
def build_authentication_abilities
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 85991c38363..a40330d853f 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -194,8 +194,8 @@ describe Gitlab::PathRegex do
end
end
- describe '.root_namespace_path_regex' do
- subject { described_class.root_namespace_path_regex }
+ describe '.root_namespace_route_regex' do
+ subject { %r{\A#{described_class.root_namespace_route_regex}/\z} }
it 'rejects top level routes' do
expect(subject).not_to match('admin/')
@@ -318,8 +318,8 @@ describe Gitlab::PathRegex do
end
end
- describe '.project_path_regex' do
- subject { described_class.project_path_regex }
+ describe '.project_route_regex' do
+ subject { %r{\A#{described_class.project_route_regex}/\z} }
it 'accepts top level routes' do
expect(subject).to match('admin/')
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 2e7a0265a0b..dc2bb5b9747 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -465,4 +465,21 @@ describe Gitlab::Workhorse do
end
end
end
+
+ describe '.send_url' do
+ let(:url) { 'http://example.com' }
+
+ subject { described_class.send_url(url) }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("send-url")
+ expect(params).to eq({
+ 'URL' => url,
+ 'AllowRedirects' => false
+ }.deep_stringify_keys)
+ end
+ end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f5b3b4a9fc5..0b3d5c6a0bd 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -675,7 +675,7 @@ describe Ci::Build do
context 'build is erasable' do
context 'new artifacts' do
- let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+ let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) }
describe '#erase' do
before do
@@ -709,7 +709,7 @@ describe Ci::Build do
end
describe '#erased?' do
- let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+ let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) }
subject { build.erased? }
context 'job has not been erased' do
@@ -744,7 +744,7 @@ describe Ci::Build do
context 'old artifacts' do
context 'build is erasable' do
context 'new artifacts' do
- let!(:build) { create(:ci_build, :trace, :success, :legacy_artifacts) }
+ let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
describe '#erase' do
before do
@@ -778,7 +778,7 @@ describe Ci::Build do
end
describe '#erased?' do
- let!(:build) { create(:ci_build, :trace, :success, :legacy_artifacts) }
+ let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
subject { build.erased? }
context 'job has not been erased' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 0e18a326c68..a2bd36537e6 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -12,6 +12,9 @@ describe Ci::JobArtifact do
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
+ it { is_expected.to delegate_method(:open).to(:file) }
+ it { is_expected.to delegate_method(:exists?).to(:file) }
+
describe '#set_size' do
it 'sets the size' do
expect(artifact.size).to eq(106365)
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 3106207811a..8cb50d7465c 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -39,7 +39,7 @@ describe Group, 'Routable' do
create(:group, parent: group, path: 'xyz')
duplicate = build(:project, namespace: group, path: 'xyz')
- expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid')
+ expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 5ea4acb6687..338fb314ee9 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -41,7 +41,6 @@ describe Group do
describe 'validations' do
it { is_expected.to validate_presence_of :name }
- it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
it { is_expected.to validate_presence_of :path }
it { is_expected.not_to validate_presence_of :owner }
it { is_expected.to validate_presence_of :two_factor_grace_period }
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 5e126bc4bea..191b60e4383 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -15,7 +15,6 @@ describe Namespace do
describe 'validations' do
it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) }
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 6980ba335b8..622d8844a72 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -408,7 +408,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'if the services is active' do
it 'should return a message' do
- expect(kubernetes_service.deprecation_message).to match(/Your cluster information on this page is still editable/)
+ expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/)
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index da940571bc1..a63f5d6d5a1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -129,7 +129,6 @@ describe Project do
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
it { is_expected.to validate_length_of(:path).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 88f54fd10e5..dfac82b327a 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -27,7 +27,7 @@ describe Route do
redirect.save!(validate: false)
expect(new_route.valid?).to be_falsey
- expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ expect(new_route.errors.first[1]).to eq('has been taken before')
end
end
@@ -49,7 +49,7 @@ describe Route do
redirect.save!(validate: false)
expect(route.valid?).to be_falsey
- expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ expect(route.errors.first[1]).to eq('has been taken before')
end
end
@@ -368,7 +368,7 @@ describe Route do
group2.path = 'foo'
group2.valid?
- expect(group2.errors["route.path"].first).to eq('foo has been taken before. Please use another one')
+ expect(group2.errors[:path]).to eq(['has been taken before'])
end
end
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 0dcaa026332..36b8e5d304f 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -43,6 +43,18 @@ describe Upload do
.to(a_string_matching(/\A\h{64}\z/))
end
end
+
+ describe 'after_destroy' do
+ context 'uploader is FileUploader-based' do
+ subject { create(:upload, :issuable_upload) }
+
+ it 'calls delete_file!' do
+ is_expected.to receive(:delete_file!)
+
+ subject.destroy
+ end
+ end
+ end
end
describe '#absolute_path' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 568aab8530e..cb02d526a98 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -101,7 +101,7 @@ describe User do
user = build(:user, username: 'dashboard')
expect(user).not_to be_valid
- expect(user.errors.values).to eq [['dashboard is a reserved name']]
+ expect(user.errors.messages[:username]).to eq ['dashboard is a reserved name']
end
it 'allows child names' do
@@ -116,12 +116,6 @@ describe User do
expect(user).to be_valid
end
- it 'validates uniqueness' do
- user = build(:user)
-
- expect(user).to validate_uniqueness_of(:username).case_insensitive
- end
-
context 'when username is changed' do
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) }
@@ -132,6 +126,35 @@ describe User do
expect(user.errors.messages[:username].first).to match('cannot be changed if a personal project has container registry tags')
end
end
+
+ context 'when the username was used by another user before' do
+ let(:username) { 'foo' }
+ let!(:other_user) { create(:user, username: username) }
+
+ before do
+ other_user.username = 'bar'
+ other_user.save!
+ end
+
+ it 'is invalid' do
+ user = build(:user, username: username)
+
+ expect(user).not_to be_valid
+ expect(user.errors.full_messages).to eq(['Username has been taken before'])
+ end
+ end
+
+ context 'when the username is in use by another user' do
+ let(:username) { 'foo' }
+ let!(:other_user) { create(:user, username: username) }
+
+ it 'is invalid' do
+ user = build(:user, username: username)
+
+ expect(user).not_to be_valid
+ expect(user.errors.full_messages).to eq(['Username has already been taken'])
+ end
+ end
end
it 'has a DB-level NOT NULL constraint on projects_limit' do
@@ -1433,28 +1456,34 @@ describe User do
describe '#sort' do
before do
described_class.delete_all
- @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
- @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
- @user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta'
+ @user = create :user, created_at: Date.today, current_sign_in_at: Date.today, name: 'Alpha'
+ @user1 = create :user, created_at: Date.today - 1, current_sign_in_at: Date.today - 1, name: 'Omega'
+ @user2 = create :user, created_at: Date.today - 2, name: 'Beta'
end
context 'when sort by recent_sign_in' do
- it 'sorts users by the recent sign-in time' do
- expect(described_class.sort('recent_sign_in').first).to eq(@user)
+ let(:users) { described_class.sort('recent_sign_in') }
+
+ it 'sorts users by recent sign-in time' do
+ expect(users.first).to eq(@user)
+ expect(users.second).to eq(@user1)
end
it 'pushes users who never signed in to the end' do
- expect(described_class.sort('recent_sign_in').third).to eq(@user2)
+ expect(users.third).to eq(@user2)
end
end
context 'when sort by oldest_sign_in' do
+ let(:users) { described_class.sort('oldest_sign_in') }
+
it 'sorts users by the oldest sign-in time' do
- expect(described_class.sort('oldest_sign_in').first).to eq(@user1)
+ expect(users.first).to eq(@user1)
+ expect(users.second).to eq(@user)
end
it 'pushes users who never signed in to the end' do
- expect(described_class.sort('oldest_sign_in').third).to eq(@user2)
+ expect(users.third).to eq(@user2)
end
end
@@ -2264,17 +2293,17 @@ describe User do
end
context 'when there is a validation error (namespace name taken) while updating namespace' do
- let!(:conflicting_namespace) { create(:group, name: new_username, path: 'quz') }
+ let!(:conflicting_namespace) { create(:group, path: new_username) }
it 'causes the user save to fail' do
expect(user.update_attributes(username: new_username)).to be_falsey
- expect(user.namespace.errors.messages[:name].first).to eq('has already been taken')
+ expect(user.namespace.errors.messages[:path].first).to eq('has already been taken')
end
it 'adds the namespace errors to the user' do
user.update_attributes(username: new_username)
- expect(user.errors.full_messages.first).to eq('Namespace name has already been taken')
+ expect(user.errors.full_messages.first).to eq('Username has already been taken')
end
end
end
@@ -2617,7 +2646,7 @@ describe User do
it 'should raise an ActiveRecord::RecordInvalid exception' do
user2 = build(:user, username: 'foo')
- expect { user2.save! }.to raise_error(ActiveRecord::RecordInvalid, /Route path foo has been taken before. Please use another one, Route is invalid/)
+ expect { user2.save! }.to raise_error(ActiveRecord::RecordInvalid, /Username has been taken before/)
end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 4a1d12cd448..d53ba497ed1 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -364,18 +364,34 @@ describe WikiPage do
end
describe "#versions" do
- before do
- create_page("Update", "content")
- @page = wiki.find_page("Update")
+ shared_examples 'wiki page versions' do
+ let(:page) { wiki.find_page("Update") }
+
+ before do
+ create_page("Update", "content")
+ end
+
+ after do
+ destroy_page("Update")
+ end
+
+ it "returns an array of all commits for the page" do
+ 3.times { |i| page.update(content: "content #{i}") }
+
+ expect(page.versions.count).to eq(4)
+ end
+
+ it 'returns instances of WikiPageVersion' do
+ expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) )
+ end
end
- after do
- destroy_page("Update")
+ context 'when Gitaly is enabled' do
+ it_behaves_like 'wiki page versions'
end
- it "returns an array of all commits for the page" do
- 3.times { |i| @page.update(content: "content #{i}") }
- expect(@page.versions.count).to eq(4)
+ context 'when Gitaly is disabled', :disable_gitaly do
+ it_behaves_like 'wiki page versions'
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 884a258fd12..ea6b0a71849 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -368,7 +368,7 @@ describe API::Internal do
context 'project as /namespace/project' do
it do
- pull(key, project_with_repo_path('/' + project.full_path))
+ push(key, project_with_repo_path('/' + project.full_path))
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
@@ -379,7 +379,7 @@ describe API::Internal do
context 'project as namespace/project' do
it do
- pull(key, project_with_repo_path(project.full_path))
+ push(key, project_with_repo_path(project.full_path))
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
@@ -807,14 +807,27 @@ describe API::Internal do
context 'with a redirected data' do
it 'returns redirected message on the response' do
- project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'foo/baz', 'http')
- project_moved.add_redirect_message
+ project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz')
+ project_moved.add_message
post api("/internal/post_receive"), valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response["redirected_message"]).to be_present
- expect(json_response["redirected_message"]).to eq(project_moved.redirect_message)
+ expect(json_response["redirected_message"]).to eq(project_moved.message)
+ end
+ end
+
+ context 'with new project data' do
+ it 'returns new project message on the response' do
+ project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http')
+ project_created.add_message
+
+ post api("/internal/post_receive"), valid_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response["project_created_message"]).to be_present
+ expect(json_response["project_created_message"]).to eq(project_created.message)
end
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index f8d0b63afec..6192bbd4abb 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -446,16 +446,27 @@ describe API::Jobs do
end
describe 'GET /projects/:id/jobs/:job_id/trace' do
- let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
-
before do
get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
end
context 'authorized user' do
- it 'returns specific job trace' do
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq(job.trace.raw)
+ context 'when trace is artifact' do
+ let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ it 'returns specific job trace' do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(job.trace.raw)
+ end
+ end
+
+ context 'when trace is file' do
+ let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
+
+ it 'returns specific job trace' do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(job.trace.raw)
+ end
end
end
@@ -543,11 +554,11 @@ describe API::Jobs do
end
context 'job is erasable' do
- let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline) }
it 'erases job content' do
expect(response).to have_gitlab_http_status(201)
- expect(job).not_to have_trace
+ expect(job.trace.exist?).to be_falsy
expect(job.artifacts_file.exists?).to be_falsy
expect(job.artifacts_metadata.exists?).to be_falsy
end
@@ -561,7 +572,7 @@ describe API::Jobs do
end
context 'job is not erasable' do
- let(:job) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) }
it 'responds with forbidden' do
expect(response).to have_gitlab_http_status(403)
@@ -570,7 +581,7 @@ describe API::Jobs do
context 'when a developer erases a build' do
let(:role) { :developer }
- let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline, user: owner) }
+ let(:job) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline, user: owner) }
context 'when the build was created by the developer' do
let(:owner) { user }
@@ -593,7 +604,7 @@ describe API::Jobs do
context 'artifacts did not expire' do
let(:job) do
- create(:ci_build, :trace, :artifacts, :success,
+ create(:ci_build, :trace_artifact, :artifacts, :success,
project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index c5c0b0c2867..0bd88748479 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -638,7 +638,7 @@ describe API::Runner do
end
describe 'PUT /api/v4/jobs/:id' do
- let(:job) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) }
+ let(:job) { create(:ci_build, :pending, :trace_live, pipeline: pipeline, runner_id: runner.id) }
before do
job.run!
@@ -680,11 +680,17 @@ describe API::Runner do
end
context 'when tace is given' do
- it 'updates a running build' do
- update_job(trace: 'BUILD TRACE UPDATED')
+ it 'creates a trace artifact' do
+ allow_any_instance_of(BuildFinishedWorker).to receive(:perform).with(job.id) do
+ CreateTraceArtifactWorker.new.perform(job.id)
+ end
+
+ update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
+ job.reload
expect(response).to have_gitlab_http_status(200)
- expect(job.reload.trace.raw).to eq 'BUILD TRACE UPDATED'
+ expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
+ expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
end
end
@@ -713,7 +719,7 @@ describe API::Runner do
end
describe 'PATCH /api/v4/jobs/:id/trace' do
- let(:job) { create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) }
let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
let(:update_interval) { 10.seconds.to_i }
@@ -774,7 +780,7 @@ describe API::Runner do
context 'when project for the build has been deleted' do
let(:job) do
- create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) do |job|
+ create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) do |job|
job.project.update(pending_delete: true)
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 2428e63e149..f406d2ffb22 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -199,6 +199,24 @@ describe API::Users do
expect(json_response.size).to eq(1)
expect(json_response.first['username']).to eq(user.username)
end
+
+ it 'returns the correct order when sorted by id' do
+ admin
+ user
+
+ get api('/users', admin), { order_by: 'id', sort: 'asc' }
+
+ expect(response).to match_response_schema('public_api/v4/user/admins')
+ expect(json_response.size).to eq(2)
+ expect(json_response.first['id']).to eq(admin.id)
+ expect(json_response.last['id']).to eq(user.id)
+ end
+
+ it 'returns 400 when provided incorrect sort params' do
+ get api('/users', admin), { order_by: 'magic', sort: 'asc' }
+
+ expect(response).to have_gitlab_http_status(400)
+ end
end
end
diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index 3f92288fef0..79041c6a792 100644
--- a/spec/requests/api/v3/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -352,7 +352,7 @@ describe API::V3::Builds do
end
describe 'GET /projects/:id/builds/:build_id/trace' do
- let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
+ let(:build) { create(:ci_build, :trace_live, pipeline: pipeline) }
before do
get v3_api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
@@ -447,7 +447,7 @@ describe API::V3::Builds do
end
context 'job is erasable' do
- let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
+ let(:build) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline) }
it 'erases job content' do
expect(response.status).to eq 201
@@ -463,7 +463,7 @@ describe API::V3::Builds do
end
context 'job is not erasable' do
- let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
+ let(:build) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) }
it 'responds with forbidden' do
expect(response.status).to eq 403
@@ -478,7 +478,7 @@ describe API::V3::Builds do
context 'artifacts did not expire' do
let(:build) do
- create(:ci_build, :trace, :artifacts, :success,
+ create(:ci_build, :trace_artifact, :artifacts, :success,
project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 27bd22d6bca..2e2dccdafad 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -107,15 +107,39 @@ describe 'Git HTTP requests' do
let(:user) { create(:user) }
context "when the project doesn't exist" do
- let(:path) { 'doesnt/exist.git' }
+ context "when namespace doesn't exist" do
+ let(:path) { 'doesnt/exist.git' }
- it_behaves_like 'pulls require Basic HTTP Authentication'
- it_behaves_like 'pushes require Basic HTTP Authentication'
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
- context 'when authenticated' do
- it 'rejects downloads and uploads with 404 Not Found' do
- download_or_upload(path, user: user.username, password: user.password) do |response|
- expect(response).to have_gitlab_http_status(:not_found)
+ context 'when authenticated' do
+ it 'rejects downloads and uploads with 404 Not Found' do
+ download_or_upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ context 'when namespace exists' do
+ let(:path) { "#{user.namespace.path}/new-project.git"}
+
+ context 'when authenticated' do
+ it 'creates a new project under the existing namespace' do
+ expect do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end.to change { user.projects.count }.by(1)
+ end
+
+ it 'rejects push with 422 Unprocessable Entity when project is invalid' do
+ path = "#{user.namespace.path}/new.git"
+
+ push_get(path, user: user.username, password: user.password)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
@@ -596,7 +620,7 @@ describe 'Git HTTP requests' do
push_get(path, env)
expect(response).to have_gitlab_http_status(:forbidden)
- expect(response.body).to eq(git_access_error(:upload))
+ expect(response.body).to eq(git_access_error(:auth_upload))
end
# We are "authenticated" as CI using a valid token here. But we are
@@ -636,7 +660,7 @@ describe 'Git HTTP requests' do
push_get path, env
expect(response).to have_gitlab_http_status(:forbidden)
- expect(response.body).to eq(git_access_error(:upload))
+ expect(response.body).to eq(git_access_error(:auth_upload))
end
end
diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb
new file mode 100644
index 00000000000..847a88920fe
--- /dev/null
+++ b/spec/services/ci/create_trace_artifact_service_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Ci::CreateTraceArtifactService do
+ describe '#execute' do
+ subject { described_class.new(nil, nil).execute(job) }
+
+ let(:job) { create(:ci_build) }
+
+ context 'when the job does not have trace artifact' do
+ context 'when the job has a trace file' do
+ before do
+ allow_any_instance_of(Gitlab::Ci::Trace)
+ .to receive(:default_path) { expand_fixture_path('trace/sample_trace') }
+
+ allow_any_instance_of(JobArtifactUploader).to receive(:move_to_cache) { false }
+ allow_any_instance_of(JobArtifactUploader).to receive(:move_to_store) { false }
+ end
+
+ it 'creates trace artifact' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(1)
+
+ expect(job.job_artifacts_trace.read_attribute(:file)).to eq('sample_trace')
+ end
+
+ context 'when the job has already had trace artifact' do
+ before do
+ create(:ci_job_artifact, :trace, job: job)
+ end
+
+ it 'does not create trace artifact' do
+ expect { subject }.not_to change { Ci::JobArtifact.count }
+ end
+ end
+ end
+
+ context 'when the job does not have a trace file' do
+ it 'does not create trace artifact' do
+ expect { subject }.not_to change { Ci::JobArtifact.count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index cea5c8a6c05..db9c216d3f4 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -21,7 +21,8 @@ describe Ci::RetryBuildService do
%i[id status user token coverage trace runner artifacts_expire_at
artifacts_file artifacts_metadata artifacts_size 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].freeze
+ erased_at auto_canceled_by job_artifacts job_artifacts_archive
+ job_artifacts_metadata job_artifacts_trace].freeze
IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections
@@ -35,7 +36,7 @@ describe Ci::RetryBuildService do
let(:build) do
create(:ci_build, :failed, :artifacts, :expired, :erased,
:queued, :coverage, :tags, :allowed_to_fail, :on_tag,
- :triggered, :trace, :teardown_environment,
+ :triggered, :trace_artifact, :teardown_environment,
description: 'my-job', stage: 'test', stage_id: stage.id,
pipeline: pipeline, auto_canceled_by: another_pipeline)
end
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
new file mode 100644
index 00000000000..030263b1502
--- /dev/null
+++ b/spec/services/files/create_service_spec.rb
@@ -0,0 +1,78 @@
+require "spec_helper"
+
+describe Files::CreateService do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:file_content) { 'Test file content' }
+ let(:branch_name) { project.default_branch }
+ let(:start_branch) { branch_name }
+
+ let(:commit_params) do
+ {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: file_content,
+ file_content_encoding: "text",
+ start_project: project,
+ start_branch: start_branch,
+ branch_name: branch_name
+ }
+ end
+
+ subject { described_class.new(project, user, commit_params) }
+
+ before do
+ project.add_master(user)
+ end
+
+ describe "#execute" do
+ context 'when file matches LFS filter' do
+ let(:file_path) { 'test_file.lfs' }
+ let(:branch_name) { 'lfs' }
+
+ context 'with LFS disabled' do
+ it 'skips gitattributes check' do
+ expect(repository).not_to receive(:attributes_at)
+
+ subject.execute
+ end
+
+ it "doesn't create LFS pointers" do
+ subject.execute
+
+ blob = repository.blob_at('lfs', file_path)
+
+ expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1')
+ expect(blob.data).to eq(file_content)
+ end
+ end
+
+ context 'with LFS enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'creates an LFS pointer' do
+ subject.execute
+
+ blob = repository.blob_at('lfs', file_path)
+
+ expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1')
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.execute
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+
+ it 'links the LfsObject to the project' do
+ expect do
+ subject.execute
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index bcc01b087f3..e1c873f8c1e 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -177,7 +177,7 @@ describe Groups::TransferService, :postgresql do
it 'should add an error on group' do
transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Validation failed: Route path has already been taken, Route is invalid')
+ expect(transfer_service.error).to eq('Transfer failed: Validation failed: Path has already been taken')
end
end
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index fc1c3d67203..757c31ab692 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -108,7 +108,7 @@ describe MergeRequests::RebaseService do
context 'git commands', :disable_gitaly do
it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3)
- .with(anything, anything, hash_including('GL_REPOSITORY'))
+ .with(anything, anything, hash_including('GL_REPOSITORY'), anything)
.and_return(['', 0])
service.execute(merge_request)
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index bb0e274c93e..6b8f9619bc4 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::GitlabProjectsImportService do
- set(:namespace) { build(:namespace) }
+ set(:namespace) { create(:namespace) }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) }
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index f8d4a47b212..a4b7fe4674f 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -21,13 +21,13 @@ describe Users::UpdateService do
end
it 'includes namespace error messages' do
- create(:group, name: 'taken', path: 'something_else')
+ create(:group, path: 'taken')
result = {}
expect do
result = update_user(user, { username: 'taken' })
end.not_to change { user.reload.username }
expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Namespace name has already been taken')
+ expect(result[:message]).to eq('Username has already been taken')
end
def update_user(user, opts)
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index a559681a079..6a92e7fae51 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -48,6 +48,29 @@ describe FileUploader do
end
end
+ describe 'callbacks' do
+ describe '#prune_store_dir after :remove' do
+ before do
+ uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+ end
+
+ def store_dir
+ File.expand_path(uploader.store_dir, uploader.root)
+ end
+
+ it 'is called' do
+ expect(uploader).to receive(:prune_store_dir).once
+
+ uploader.remove!
+ end
+
+ it 'prune the store directory' do
+ expect { uploader.remove! }
+ .to change { File.exist?(store_dir) }.from(true).to(false)
+ end
+ end
+ end
+
describe '#secret' do
it 'generates a secret if none is provided' do
expect(described_class).to receive(:generate_secret).and_return('secret')
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index d606404e95d..5612ec7e661 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -11,6 +11,33 @@ describe JobArtifactUploader do
cache_dir: %r[artifacts/tmp/cache],
work_dir: %r[artifacts/tmp/work]
+ describe '#open' do
+ subject { uploader.open }
+
+ context 'when trace is stored in File storage' do
+ context 'when file exists' do
+ let(:file) do
+ fixture_file_upload(
+ Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
+ end
+
+ before do
+ uploader.store!(file)
+ end
+
+ it 'returns io stream' do
+ is_expected.to be_a(IO)
+ end
+ end
+
+ context 'when file does not exist' do
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+ end
+
context 'file is stored in valid local_path' do
let(:file) do
fixture_file_upload(
diff --git a/spec/validators/user_path_validator_spec.rb b/spec/validators/user_path_validator_spec.rb
deleted file mode 100644
index a46089cc24f..00000000000
--- a/spec/validators/user_path_validator_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe UserPathValidator do
- let(:validator) { described_class.new(attributes: [:username]) }
-
- describe '.valid_path?' do
- it 'handles invalid utf8' do
- expect(described_class.valid_path?("a\0weird\255path")).to be_falsey
- end
- end
-
- describe '#validates_each' do
- it 'adds a message when the path is not in the correct format' do
- user = build(:user)
-
- validator.validate_each(user, :username, "Path with spaces, and comma's!")
-
- expect(user.errors[:username]).to include(Gitlab::PathRegex.namespace_format_message)
- end
-
- it 'adds a message when the path is reserved when creating' do
- user = build(:user, username: 'help')
-
- validator.validate_each(user, :username, 'help')
-
- expect(user.errors[:username]).to include('help is a reserved name')
- end
-
- it 'adds a message when the path is reserved when updating' do
- user = create(:user)
- user.username = 'help'
-
- validator.validate_each(user, :username, 'help')
-
- expect(user.errors[:username]).to include('help is a reserved name')
- end
- end
-end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index 1a7ffd5cdbf..c7ff8cf3b92 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -6,17 +6,15 @@ describe BuildFinishedWorker do
let!(:build) { create(:ci_build) }
it 'calculates coverage and calls hooks' do
- expect(BuildCoverageWorker)
+ expect(BuildTraceSectionsWorker)
.to receive(:new).ordered.and_call_original
- expect(BuildHooksWorker)
+ expect(BuildCoverageWorker)
.to receive(:new).ordered.and_call_original
- expect(BuildTraceSectionsWorker)
- .to receive(:perform_async)
- expect_any_instance_of(BuildCoverageWorker)
- .to receive(:perform)
- expect_any_instance_of(BuildHooksWorker)
- .to receive(:perform)
+ expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform)
+ expect_any_instance_of(BuildCoverageWorker).to receive(:perform)
+ expect(BuildHooksWorker).to receive(:perform_async)
+ expect(CreateTraceArtifactWorker).to receive(:perform_async)
described_class.new.perform(build.id)
end
diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/create_trace_artifact_worker_spec.rb
new file mode 100644
index 00000000000..854abd9cca7
--- /dev/null
+++ b/spec/workers/create_trace_artifact_worker_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe CreateTraceArtifactWorker do
+ describe '#perform' do
+ subject { described_class.new.perform(job&.id) }
+
+ context 'when job is found' do
+ let(:job) { create(:ci_build) }
+
+ it 'executes service' do
+ expect_any_instance_of(Ci::CreateTraceArtifactService)
+ .to receive(:execute).with(job)
+
+ subject
+ end
+ end
+
+ context 'when job is not found' do
+ let(:job) { nil }
+
+ it 'does not execute service' do
+ expect_any_instance_of(Ci::CreateTraceArtifactService)
+ .not_to receive(:execute)
+
+ subject
+ end
+ end
+ end
+end