diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-09 12:07:45 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-09 12:07:45 +0000 |
commit | ac1dca43baa7b3b1ac7d60d89ad60fdeefed0b80 (patch) | |
tree | 33aa23ddf7f18ddbfba3d006041c460de88583b7 | |
parent | f4186a753b86625a83e8499af14b5badd63a2ac2 (diff) | |
download | gitlab-ce-ac1dca43baa7b3b1ac7d60d89ad60fdeefed0b80.tar.gz |
Add latest changes from gitlab-org/gitlab@master
35 files changed, 374 insertions, 68 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cf8551989cb..f5cef674375 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,6 +12,24 @@ stages: - post-qa - pages +workflow: + rules: + # If `$FORCE_GITLAB_CI` is set, create a pipeline. + - if: '$FORCE_GITLAB_CI' + # For merge requests, create a pipeline. + - if: '$CI_MERGE_REQUEST_IID' + # For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). + - if: '$CI_COMMIT_BRANCH == "master"' + # For tags, create a pipeline. + - if: '$CI_COMMIT_TAG' + # If `$GITLAB_INTERNAL` isn't set, don't create a pipeline. + - if: '$GITLAB_INTERNAL == null' + when: never + # For stable, auto-deploy, and security branches, create a pipeline. + - if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/' + - if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/' + - if: '$CI_COMMIT_BRANCH =~ /^security\//' + variables: RAILS_ENV: "test" NODE_ENV: "test" diff --git a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue index 1b09fe1b370..3c18608eb75 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue @@ -45,10 +45,13 @@ export default { :class="{ active: isActive }" class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px" > - <div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }"> + <div + class="stage-nav-item-cell stage-name w-50 pr-2" + :class="{ 'font-weight-bold': isActive }" + > {{ title }} </div> - <div class="stage-nav-item-cell stage-median mr-4"> + <div class="stage-nav-item-cell stage-median w-50"> <template v-if="isUserAllowed"> <span v-if="hasValue">{{ value }}</span> <span v-else class="stage-empty">{{ __('Not enough data') }}</span> diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 89b673397a2..b1d79a41ba7 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -51,11 +51,11 @@ } .stage-header { - width: 18.5%; + width: 20.5%; } .median-header { - width: 21.5%; + width: 19.5%; } .event-header { diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index bb46345d362..f80e891a558 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -28,4 +28,11 @@ module SnippetsActions def convert_line_endings(content) params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n") end + + def check_repository_error + repository_error = snippet.errors.delete(:repository) + + flash.now[:alert] = repository_error if repository_error + recaptcha_check_with_fallback(repository_error.nil?) { render :edit } + end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 8317d9fc6d5..241df8d95d7 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -62,7 +62,7 @@ class Projects::SnippetsController < Projects::ApplicationController service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet) @snippet = service_response.payload[:snippet] - recaptcha_check_with_fallback { render :edit } + check_repository_error end def show diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb index 5ce2ed77417..35ea77183b8 100644 --- a/app/controllers/repositories/git_http_controller.rb +++ b/app/controllers/repositories/git_http_controller.rb @@ -12,6 +12,8 @@ module Repositories rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception + before_action :snippet_request_allowed? + # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) def info_refs @@ -116,6 +118,12 @@ module Repositories def log_user_activity Users::ActivityService.new(user).execute end + + def snippet_request_allowed? + if repo_type.snippet? && Feature.disabled?(:version_snippets, user) + render plain: 'The project you were looking for could not be found.', status: :not_found + end + end end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index e6840e46226..3f8b13dbcdd 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -64,7 +64,7 @@ class SnippetsController < ApplicationController service_response = Snippets::UpdateService.new(nil, current_user, update_params).execute(@snippet) @snippet = service_response.payload[:snippet] - recaptcha_check_with_fallback { render :edit } + check_repository_error end def show diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 78d815e5858..7300283f086 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -260,12 +260,14 @@ module Issuable highest_priority = highest_label_priority(params).to_sql - select_columns = [ - "#{table_name}.*", - "(#{highest_priority}) AS highest_priority" - ] + extra_select_columns + # When using CTE make sure to select the same columns that are on the group_by clause. + # This prevents errors when ignored columns are present in the database. + issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*" - select(select_columns.join(', ')) + extra_select_columns = extra_select_columns.unshift("(#{highest_priority}) AS highest_priority") + + select(issuable_columns) + .select(extra_select_columns) .group(issue_grouping_columns(use_cte: with_cte)) .reorder(Gitlab::Database.nulls_last_order('highest_priority', direction)) end @@ -301,7 +303,7 @@ module Issuable # Returns an array of arel columns def issue_grouping_columns(use_cte: false) if use_cte - [arel_table[:state]] + attribute_names.map { |attr| arel_table[attr.to_sym] } + attribute_names.map { |attr| arel_table[attr.to_sym] } else arel_table[:id] end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 3ad9f2bc0bf..b2c0ceb640b 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -56,9 +56,14 @@ class PipelineSerializer < BaseSerializer :manual_actions, :scheduled_actions, :artifacts, - :merge_request, :user, { + merge_request: { + source_project: [:route, { namespace: :route }], + target_project: [:route, { namespace: :route }] + } + }, + { pending_builds: :project, project: [:route, { namespace: :route }], artifacts: { diff --git a/app/services/snippets/update_service.rb b/app/services/snippets/update_service.rb index c2949ebadbf..874357f36cc 100644 --- a/app/services/snippets/update_service.rb +++ b/app/services/snippets/update_service.rb @@ -52,7 +52,7 @@ module Snippets create_commit(snippet) if snippet.repository_exists? end rescue - snippet.errors.add(:base, 'Error updating the snippet') + snippet.errors.add(:repository, 'Error updating the snippet') false end diff --git a/changelogs/unreleased/fix-api.yml b/changelogs/unreleased/fix-api.yml new file mode 100644 index 00000000000..867c534663b --- /dev/null +++ b/changelogs/unreleased/fix-api.yml @@ -0,0 +1,5 @@ +--- +title: Support finding namespace by ID or path on fork API +merge_request: 20603 +author: leoleoasd +type: fixed diff --git a/changelogs/unreleased/fj-fix-git-error-message-update-snippet.yml b/changelogs/unreleased/fj-fix-git-error-message-update-snippet.yml new file mode 100644 index 00000000000..cfb2575ac21 --- /dev/null +++ b/changelogs/unreleased/fj-fix-git-error-message-update-snippet.yml @@ -0,0 +1,5 @@ +--- +title: Show git error message updating snippet +merge_request: 26570 +author: +type: fixed diff --git a/changelogs/unreleased/sh-optimize-pipeline-for-mrs.yml b/changelogs/unreleased/sh-optimize-pipeline-for-mrs.yml new file mode 100644 index 00000000000..fd604819982 --- /dev/null +++ b/changelogs/unreleased/sh-optimize-pipeline-for-mrs.yml @@ -0,0 +1,5 @@ +--- +title: Fix N+1 queries for PipelinesController#index.json +merge_request: 26643 +author: +type: performance diff --git a/doc/administration/git_protocol.md b/doc/administration/git_protocol.md index b15c3160459..a8e785f9344 100644 --- a/doc/administration/git_protocol.md +++ b/doc/administration/git_protocol.md @@ -35,10 +35,14 @@ the SSH configuration of your server by adding the line below to the `/etc/ssh/s AcceptEnv GIT_PROTOCOL ``` -Once configured, restart the SSH daemon. In Ubuntu, run: +Once configured, restart the SSH daemon for the change to take effect: ```shell -sudo service ssh restart +# CentOS 6 / RHEL 6 +sudo service sshd restart + +# All other supported distributions +sudo systemctl restart ssh ``` ## Instructions diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md index 130ed9f3b25..9b5cabae39a 100644 --- a/doc/administration/high_availability/README.md +++ b/doc/administration/high_availability/README.md @@ -202,14 +202,8 @@ On different cloud vendors a best effort like for like can be used. and another for the Queues and Shared State classes respectively. We also recommend that you run the Redis Sentinel clusters separately as well for each Redis Cluster. -[^4]: For data objects such as LFS, Uploads, Artifacts, etc... We recommend a Cloud Object Storage - where possible over NFS due to better performance and availability. Several types of objects - are supported for S3 storage - [Job artifacts](../job_artifacts.md#using-object-storage), - [LFS](../lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage), - [Uploads](../uploads.md#using-object-storage-core-only), - [Merge Request Diffs](../merge_request_diffs.md#using-object-storage), - [Packages](../packages/index.md#using-object-storage) (Optional Feature), - [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (Optional Feature). +[^4]: For data objects such as LFS, Uploads, Artifacts, etc. We recommend a [Cloud Object Storage service](object_storage.md) + over NFS where possible, due to better performance and availability. [^5]: NFS can be used as an alternative for both repository data (replacing Gitaly) and object storage but this isn't typically recommended for performance reasons. Note however it is required for diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 26c83ab6034..7187c97ef13 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -736,10 +736,14 @@ To enable the read-only mode: This will set the Container Registry into the read only mode. -1. Next, trigger the garbage collect command: +1. Next, trigger one of the garbage collect commands: ```sh + # Recycling unused tags sudo /opt/gitlab/embedded/bin/registry garbage-collect /var/opt/gitlab/registry/config.yml + + # Removing unused layers not referenced by manifests + sudo /opt/gitlab/embedded/bin/registry garbage-collect -m /var/opt/gitlab/registry/config.yml ``` This will start the garbage collection, which might take some time to complete. diff --git a/doc/api/projects.md b/doc/api/projects.md index 5b66f0dd5d3..5b769ca4ce9 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1207,7 +1207,9 @@ POST /projects/:id/fork | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | -| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to | +| `namespace` | integer/string | no | (deprecated) The ID or path of the namespace that the project will be forked to | +| `namespace_id` | integer | no | The ID of the namespace that the project will be forked to | +| `namespace_path` | string | no | The path of the namespace that the project will be forked to | | `path` | string | no | The path that will be assigned to the resultant project after forking | | `name` | string | no | The name that will be assigned to the resultant project after forking | diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 2c81c41360a..ec0e5df6a92 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -42,7 +42,7 @@ The default image is currently `registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34`. It includes Ruby 2.6.5, Go 1.12, Git 2.24, Git LFS 2.9, Chrome 73, Node 12, Yarn 1.21, -PostgreSQL 9.6, and Graphics Magick 1.3.33. +PostgreSQL 9.6, and Graphics Magick 1.3.34. The images used in our pipelines are configured in the [`gitlab-org/gitlab-build-images`](https://gitlab.com/gitlab-org/gitlab-build-images) @@ -61,8 +61,8 @@ each pipeline includes default variables defined in ## Common job definitions Most of the jobs [extend from a few CI definitions](../ci/yaml/README.md#extends) -that are scoped to a single -[configuration parameter](../ci/yaml/README.md#configuration-parameters). +defined in [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/global.gitlab-ci.yml) +that are scoped to a single [configuration parameter](../ci/yaml/README.md#configuration-parameters). | Job definitions | Description | |------------------|-------------| @@ -72,10 +72,27 @@ that are scoped to a single | `.default-cache` | Allows a job to use a default `cache` definition suitable for Ruby/Rails and frontend tasks. | | `.use-pg9` | Allows a job to use the `postgres:9.6.17` and `redis:alpine` services. | | `.use-pg10` | Allows a job to use the `postgres:10.12` and `redis:alpine` services. | +| `.use-pg11` | Allows a job to use the `postgres:11.6` and `redis:alpine` services. | | `.use-pg9-ee` | Same as `.use-pg9` but also use the `docker.elastic.co/elasticsearch/elasticsearch:6.4.2` services. | | `.use-pg10-ee` | Same as `.use-pg10` but also use the `docker.elastic.co/elasticsearch/elasticsearch:6.4.2` services. | +| `.use-pg11-ee` | Same as `.use-pg11` but also use the `docker.elastic.co/elasticsearch/elasticsearch:6.4.2` services. | | `.as-if-foss` | Simulate the FOSS project by setting the `FOSS_ONLY='1'` environment variable. | +## `workflow:rules` + +We're using the [`workflow:rules` keyword](../ci/yaml/README.md#workflowrules) to +define default rules to determine whether or not a pipeline is created. + +These rules are defined in <https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml> +and are as follows: + +1. If `$FORCE_GITLAB_CI` is set, create a pipeline. +1. For merge requests, create a pipeline. +1. For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). +1. For tags, create a pipeline. +1. If `$GITLAB_INTERNAL` isn't set, don't create a pipeline. + 1. For stable, auto-deploy, and security branches, create a pipeline. + ## `rules`, `if:` conditions and `changes:` patterns We're using the [`rules` keyword](../ci/yaml/README.md#rules) extensively. diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 8631c8a649d..9c98ef1f2f8 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -331,7 +331,7 @@ metrics: unit: "count" ``` -This will render into: +This works by lowercasing the value of `label` and, if there are more words separated by spaces, replacing those spaces with an underscore (`_`). The transformed value is then checked against the labels of the time series returned by the Prometheus query. If a time series label is found that is equal to the transformed value, then the label value will be used and rendered in the legend like this: ![legend with label shorthand variable](img/prometheus_dashboard_label_variable_shorthand.png) diff --git a/lib/api/entities/remote_mirror.rb b/lib/api/entities/remote_mirror.rb index dde3e9dea99..18d51726bab 100644 --- a/lib/api/entities/remote_mirror.rb +++ b/lib/api/entities/remote_mirror.rb @@ -12,6 +12,9 @@ module API expose :last_successful_update_at expose :last_error expose :only_protected_branches + expose :keep_divergent_refs, if: -> (mirror, _options) do + ::Feature.enabled?(:keep_divergent_refs, mirror.project) + end end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6c3b8cb8dd8..c3b5654e217 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -142,6 +142,12 @@ module API end end + def check_namespace_access(namespace) + return namespace if can?(current_user, :read_namespace, namespace) + + not_found!('Namespace') + end + # rubocop: disable CodeReuse/ActiveRecord def find_namespace(id) if id.to_s =~ /^\d+$/ @@ -153,13 +159,15 @@ module API # rubocop: enable CodeReuse/ActiveRecord def find_namespace!(id) - namespace = find_namespace(id) + check_namespace_access(find_namespace(id)) + end - if can?(current_user, :read_namespace, namespace) - namespace - else - not_found!('Namespace') - end + def find_namespace_by_path(path) + Namespace.find_by_full_path(path) + end + + def find_namespace_by_path!(path) + check_namespace_access(find_namespace_by_path(path)) end def find_branch!(branch_name) diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 2e1891ac656..cca037b0705 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -108,6 +108,10 @@ module API # check_ip - optional, only in EE version, may limit access to # group resources based on its IP restrictions post "/allowed" do + if repo_type.snippet? && Feature.disabled?(:version_snippets, actor.user) + break response_with_status(code: 404, success: false, message: 'The project you were looking for could not be found.') + end + # It was moved to a separate method so that EE can alter its behaviour more # easily. check_allowed(params) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ca55e6ac010..3717e25d997 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -263,7 +263,9 @@ module API success Entities::Project end params do - optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + optional :namespace, type: String, desc: '(deprecated) The ID or name of the namespace that the project will be forked into' + optional :namespace_id, type: Integer, desc: 'The ID of the namespace that the project will be forked into' + optional :namespace_path, type: String, desc: 'The path of the namespace that the project will be forked into' optional :path, type: String, desc: 'The path that will be assigned to the fork' optional :name, type: String, desc: 'The name that will be assigned to the fork' end @@ -273,7 +275,15 @@ module API not_found! unless can?(current_user, :fork_project, user_project) fork_params = declared_params(include_missing: false) - fork_params[:namespace] = find_namespace!(fork_params[:namespace]) if fork_params[:namespace].present? + + fork_params[:namespace] = + if fork_params[:namespace_id].present? + find_namespace!(fork_params[:namespace_id]) + elsif fork_params[:namespace_path].present? + find_namespace_by_path!(fork_params[:namespace_path]) + elsif fork_params[:namespace].present? + find_namespace!(fork_params[:namespace]) + end service = ::Projects::ForkService.new(user_project, current_user, fork_params) @@ -285,8 +295,8 @@ module API conflict!(forked_project.errors.messages) else present forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, forked_project), - current_user: current_user + user_can_admin_project: can?(current_user, :admin_project, forked_project), + current_user: current_user end end diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb index bdaec67108d..cb8383ff08a 100644 --- a/lib/api/remote_mirrors.rb +++ b/lib/api/remote_mirrors.rb @@ -33,9 +33,11 @@ module API requires :url, type: String, desc: 'The URL for a remote mirror' optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored' + optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target' end post ':id/remote_mirrors' do create_params = declared_params(include_missing: false) + create_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project) new_mirror = user_project.remote_mirrors.create(create_params) @@ -53,12 +55,15 @@ module API requires :mirror_id, type: String, desc: 'The ID of a remote mirror' optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored' + optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target' end put ':id/remote_mirrors/:mirror_id' do mirror = user_project.remote_mirrors.find(params[:mirror_id]) mirror_params = declared_params(include_missing: false) mirror_params[:id] = mirror_params.delete(:mirror_id) + mirror_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project) + update_params = { remote_mirrors_attributes: mirror_params } result = ::Projects::UpdateService diff --git a/lib/gitlab/git_access_snippet.rb b/lib/gitlab/git_access_snippet.rb index 3956fc8a483..5817b17164e 100644 --- a/lib/gitlab/git_access_snippet.rb +++ b/lib/gitlab/git_access_snippet.rb @@ -8,7 +8,6 @@ module Gitlab authentication_mechanism: 'The authentication mechanism is not supported.', read_snippet: 'You are not allowed to read this snippet.', update_snippet: 'You are not allowed to update this snippet.', - project_not_found: 'The project you were looking for could not be found.', snippet_not_found: 'The snippet you were looking for could not be found.', repository_not_found: 'The snippet repository you were looking for could not be found.' }.freeze @@ -31,10 +30,6 @@ module Gitlab raise ForbiddenError, ERROR_MESSAGES[:authentication_mechanism] end - unless Feature.enabled?(:version_snippets, user) - raise NotFoundError, ERROR_MESSAGES[:project_not_found] - end - check_snippet_accessibility! super diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index fd33f32e877..71bb256cee9 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -38,9 +38,9 @@ describe Projects::PipelinesController do expect(response).to match_response_schema('pipeline') expect(json_response).to include('pipelines') - expect(json_response['pipelines'].count).to eq 5 - expect(json_response['count']['all']).to eq '5' - expect(json_response['count']['running']).to eq '1' + expect(json_response['pipelines'].count).to eq 6 + expect(json_response['count']['all']).to eq '6' + expect(json_response['count']['running']).to eq '2' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '3' @@ -61,7 +61,7 @@ describe Projects::PipelinesController do # There appears to be one extra query for Pipelines#has_warnings? for some reason expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1) expect(response).to have_gitlab_http_status(:ok) - expect(json_response['pipelines'].count).to eq 10 + expect(json_response['pipelines'].count).to eq 12 end end @@ -77,9 +77,9 @@ describe Projects::PipelinesController do expect(response).to match_response_schema('pipeline') expect(json_response).to include('pipelines') - expect(json_response['pipelines'].count).to eq 5 - expect(json_response['count']['all']).to eq '5' - expect(json_response['count']['running']).to eq '1' + expect(json_response['pipelines'].count).to eq 6 + expect(json_response['count']['all']).to eq '6' + expect(json_response['count']['running']).to eq '2' expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['finished']).to eq '3' @@ -99,8 +99,9 @@ describe Projects::PipelinesController do # There appears to be one extra query for Pipelines#has_warnings? for some reason expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1) + expect(response).to have_gitlab_http_status(:ok) - expect(json_response['pipelines'].count).to eq 10 + expect(json_response['pipelines'].count).to eq 12 end end @@ -139,7 +140,7 @@ describe Projects::PipelinesController do it 'returns the pipelines when the user has access' do get_pipelines_index_json - expect(json_response['pipelines'].size).to eq(5) + expect(json_response['pipelines'].size).to eq(6) end end @@ -155,18 +156,32 @@ describe Projects::PipelinesController do %w(pending running success failed canceled).each_with_index do |status, index| create_pipeline(status, project.commit("HEAD~#{index}")) end + + create_pipeline_with_merge_request end - def create_pipeline(status, sha) + def create_pipeline_with_merge_request + # New merge requests must be created with different branches, so + # let's just create new ones with random names. + branch_name = "test-#{SecureRandom.hex}" + project.repository.create_branch(branch_name, project.repository.root_ref) + mr = create(:merge_request, source_project: project, target_project: project, source_branch: branch_name) + create_pipeline(:running, project.commit('HEAD'), merge_request: mr) + end + + def create_pipeline(status, sha, merge_request: nil) user = create(:user) pipeline = create(:ci_empty_pipeline, status: status, project: project, sha: sha, - user: user) + user: user, + merge_request: merge_request) create_build(pipeline, 'build', 1, 'build', user) create_build(pipeline, 'test', 2, 'test', user) create_build(pipeline, 'deploy', 3, 'deploy', user) + + pipeline end def create_build(pipeline, stage, stage_idx, name, user = nil) diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb index 2573fdf16ff..005db748e91 100644 --- a/spec/controllers/repositories/git_http_controller_spec.rb +++ b/spec/controllers/repositories/git_http_controller_spec.rb @@ -135,6 +135,38 @@ describe Repositories::GitHttpController do end end + shared_examples 'snippet feature flag disabled behavior' do + before do + stub_feature_flags(version_snippets: false) + + request.headers.merge! auth_env(user.username, user.password, nil) + end + + describe 'GET #info_refs' do + let(:params) { container_params.merge(service: 'git-upload-pack') } + + it 'returns 404' do + get :info_refs, params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe 'POST #git_upload_pack' do + before do + allow(controller).to receive(:authenticate_user).and_return(true) + allow(controller).to receive(:verify_workhorse_api!).and_return(true) + allow(controller).to receive(:access_check).and_return(nil) + end + + it 'returns 404' do + post :git_upload_pack, params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + context 'when repository container is a project' do it_behaves_like 'info_refs behavior' do let(:user) { project.owner } @@ -158,6 +190,9 @@ describe Repositories::GitHttpController do let(:expected_class) { Gitlab::GitAccessSnippet } let(:expected_object) { personal_snippet } end + it_behaves_like 'snippet feature flag disabled behavior' do + let(:user) { personal_snippet.author } + end end context 'when repository container is a project snippet' do @@ -172,5 +207,8 @@ describe Repositories::GitHttpController do let(:expected_class) { Gitlab::GitAccessSnippet } let(:expected_object) { project_snippet } end + it_behaves_like 'snippet feature flag disabled behavior' do + let(:user) { project_snippet.author } + end end end diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb index 93a5b4a7262..0c3438575ba 100644 --- a/spec/features/projects/snippets/user_updates_snippet_spec.rb +++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe 'Projects > Snippets > User updates a snippet' do - let(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } let!(:snippet) { create(:project_snippet, project: project, author: user) } - let(:user) { create(:user) } before do stub_feature_flags(snippets_vue: false) @@ -13,16 +13,33 @@ describe 'Projects > Snippets > User updates a snippet' do sign_in(user) visit(project_snippet_path(project, snippet)) - end - it 'updates a snippet' do page.within('.detail-page-header') do first(:link, 'Edit').click end + end + it 'updates a snippet' do fill_in('project_snippet_title', with: 'Snippet new title') click_button('Save') expect(page).to have_content('Snippet new title') end + + context 'when the git operation fails' do + before do + allow_next_instance_of(Snippets::UpdateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError) + end + + fill_in('project_snippet_title', with: 'Snippet new title') + + click_button('Save') + end + + it 'renders edit page and displays the error' do + expect(page).to have_content('Error updating the snippet') + expect(page).to have_content('Edit Snippet') + end + end end diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 1d26660a4f6..706758164b2 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -8,7 +8,7 @@ describe 'User edits snippet', :js do let(:file_name) { 'test.rb' } let(:content) { 'puts "test"' } - let(:user) { create(:user) } + let_it_be(:user) { create(:user) } let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) } before do @@ -58,4 +58,21 @@ describe 'User edits snippet', :js do expect(page).to have_no_xpath("//i[@class='fa fa-lock']") expect(page).to have_xpath("//i[@class='fa fa-globe']") end + + context 'when the git operation fails' do + before do + allow_next_instance_of(Snippets::UpdateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError) + end + + fill_in 'personal_snippet_title', with: 'New Snippet Title' + + click_button('Save changes') + end + + it 'renders edit page and displays the error' do + expect(page).to have_content('Error updating the snippet') + expect(page).to have_content('Edit Snippet') + end + end end diff --git a/spec/fixtures/api/schemas/remote_mirror.json b/spec/fixtures/api/schemas/remote_mirror.json index 416b0f080d9..87bde189db5 100644 --- a/spec/fixtures/api/schemas/remote_mirror.json +++ b/spec/fixtures/api/schemas/remote_mirror.json @@ -10,7 +10,7 @@ "last_successful_update_at", "last_error", "only_protected_branches" - ], + ], "properties": { "id": { "type": "integer" }, "enabled": { "type": "boolean" }, @@ -20,7 +20,8 @@ "last_update_started_at": { "type": ["string", "null"] }, "last_successful_update_at": { "type": ["string", "null"] }, "last_error": { "type": ["string", "null"] }, - "only_protected_branches": { "type": "boolean" } + "only_protected_branches": { "type": "boolean" }, + "keep_divergent_refs": { "type": ["boolean", "null"] } }, "additionalProperties": false } diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb index a68ac8ee8fe..ba7b7da7e7d 100644 --- a/spec/lib/gitlab/git_access_snippet_spec.rb +++ b/spec/lib/gitlab/git_access_snippet_spec.rb @@ -31,12 +31,15 @@ describe Gitlab::GitAccessSnippet do end describe 'when feature flag :version_snippets is disabled' do + let(:user) { snippet.author } + before do stub_feature_flags(version_snippets: false) end - it 'does not allow push and pull access' do - expect { pull_access_check }.to raise_project_not_found + it 'allows push and pull access' do + expect { pull_access_check }.not_to raise_error + expect { push_access_check }.not_to raise_error end end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 1f20a088e4f..95513776f39 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -315,6 +315,18 @@ describe API::Internal::Base do end end + shared_examples 'snippets with disabled feature flag' do + context 'when feature flag :version_snippets is disabled' do + it 'returns 404' do + stub_feature_flags(version_snippets: false) + + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + context 'git push with personal snippet' do it 'responds with success' do push(key, personal_snippet) @@ -325,6 +337,10 @@ describe API::Internal::Base do expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") expect(user.reload.last_activity_on).to be_nil end + + it_behaves_like 'snippets with disabled feature flag' do + subject { push(key, personal_snippet) } + end end context 'git pull with personal snippet' do @@ -337,6 +353,10 @@ describe API::Internal::Base do expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") expect(user.reload.last_activity_on).to eql(Date.today) end + + it_behaves_like 'snippets with disabled feature flag' do + subject { pull(key, personal_snippet) } + end end context 'git push with project snippet' do @@ -349,6 +369,10 @@ describe API::Internal::Base do expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") expect(user.reload.last_activity_on).to be_nil end + + it_behaves_like 'snippets with disabled feature flag' do + subject { push(key, project_snippet) } + end end context 'git pull with project snippet' do @@ -361,6 +385,10 @@ describe API::Internal::Base do expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") expect(user.reload.last_activity_on).to eql(Date.today) end + + it_behaves_like 'snippets with disabled feature flag' do + subject { pull(key, project_snippet) } + end end context "git pull" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 858fdc783ee..1e8ab983b50 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2871,6 +2871,66 @@ describe API::Projects do expect(json_response['namespace']['name']).to eq(group2.name) end + context 'when namespace_id is specified' do + shared_examples_for 'forking to specified namespace_id' do + it 'forks to specified namespace_id' do + expect(response).to have_gitlab_http_status(:created) + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['id']).to eq(user2.namespace.id) + end + end + + context 'and namespace_id is specified alone' do + before do + post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id } + end + + it_behaves_like 'forking to specified namespace_id' + end + + context 'and namespace_id and namespace are both specified' do + before do + post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id, namespace: admin.namespace.id } + end + + it_behaves_like 'forking to specified namespace_id' + end + + context 'and namespace_id and namespace_path are both specified' do + before do + post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id, namespace_path: admin.namespace.path } + end + + it_behaves_like 'forking to specified namespace_id' + end + end + + context 'when namespace_path is specified' do + shared_examples_for 'forking to specified namespace_path' do + it 'forks to specified namespace_path' do + expect(response).to have_gitlab_http_status(:created) + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['path']).to eq(user2.namespace.path) + end + end + + context 'and namespace_path is specified alone' do + before do + post api("/projects/#{project.id}/fork", user2), params: { namespace_path: user2.namespace.path } + end + + it_behaves_like 'forking to specified namespace_path' + end + + context 'and namespace_path and namespace are both specified' do + before do + post api("/projects/#{project.id}/fork", user2), params: { namespace_path: user2.namespace.path, namespace: admin.namespace.path } + end + + it_behaves_like 'forking to specified namespace_path' + end + end + it 'forks to owned subgroup' do full_path = "#{group2.path}/#{group3.path}" post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path } diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb index 2186fe375ac..5b5188e024c 100644 --- a/spec/requests/api/remote_mirrors_spec.rb +++ b/spec/requests/api/remote_mirrors_spec.rb @@ -91,6 +91,10 @@ describe API::RemoteMirrors do let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } } let(:mirror) { project.remote_mirrors.first } + before do + stub_feature_flags(keep_divergent_refs: false) + end + it 'requires `admin_remote_mirror` permission' do put api(route[mirror.id], developer) @@ -102,12 +106,31 @@ describe API::RemoteMirrors do put api(route[mirror.id], user), params: { enabled: '0', - only_protected_branches: 'true' + only_protected_branches: 'true', + keep_divergent_refs: 'true' } expect(response).to have_gitlab_http_status(:success) expect(json_response['enabled']).to eq(false) expect(json_response['only_protected_branches']).to eq(true) + + # Deleted due to lack of feature availability + expect(json_response['keep_divergent_refs']).to be_nil + end + + context 'with the `keep_divergent_refs` feature enabled' do + before do + stub_feature_flags(keep_divergent_refs: { enabled: true, project: project }) + end + + it 'updates the `keep_divergent_refs` attribute' do + project.add_maintainer(user) + + put api(route[mirror.id], user), params: { keep_divergent_refs: 'true' } + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['keep_divergent_refs']).to eq(true) + end end # TODO: Remove flag: https://gitlab.com/gitlab-org/gitlab/issues/38121 diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb index 2c70cce767d..3605d3f76da 100644 --- a/spec/services/snippets/update_service_spec.rb +++ b/spec/services/snippets/update_service_spec.rb @@ -148,7 +148,7 @@ describe Snippets::UpdateService do response = subject expect(response).to be_error - expect(response.payload[:snippet].errors.full_messages).to eq ['Error updating the snippet'] + expect(response.payload[:snippet].errors.full_messages).to eq ['Repository Error updating the snippet'] end end @@ -173,7 +173,7 @@ describe Snippets::UpdateService do response = subject expect(response).to be_error - expect(response.payload[:snippet].errors.full_messages).to eq ['Error updating the snippet'] + expect(response.payload[:snippet].errors.full_messages).to eq ['Repository Error updating the snippet'] end it 'returns error if snippet does not have a snippet_repository' do |