Commit ac1dca43 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f4186a75
...@@ -12,6 +12,24 @@ stages: ...@@ -12,6 +12,24 @@ stages:
- post-qa - post-qa
- pages - 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: variables:
RAILS_ENV: "test" RAILS_ENV: "test"
NODE_ENV: "test" NODE_ENV: "test"
......
...@@ -45,10 +45,13 @@ export default { ...@@ -45,10 +45,13 @@ export default {
:class="{ active: isActive }" :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" 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 }} {{ title }}
</div> </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"> <template v-if="isUserAllowed">
<span v-if="hasValue">{{ value }}</span> <span v-if="hasValue">{{ value }}</span>
<span v-else class="stage-empty">{{ __('Not enough data') }}</span> <span v-else class="stage-empty">{{ __('Not enough data') }}</span>
......
...@@ -51,11 +51,11 @@ ...@@ -51,11 +51,11 @@
} }
.stage-header { .stage-header {
width: 18.5%; width: 20.5%;
} }
.median-header { .median-header {
width: 21.5%; width: 19.5%;
} }
.event-header { .event-header {
......
...@@ -28,4 +28,11 @@ module SnippetsActions ...@@ -28,4 +28,11 @@ module SnippetsActions
def convert_line_endings(content) def convert_line_endings(content)
params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n") params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n")
end 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 end
...@@ -62,7 +62,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -62,7 +62,7 @@ class Projects::SnippetsController < Projects::ApplicationController
service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet) service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet)
@snippet = service_response.payload[:snippet] @snippet = service_response.payload[:snippet]
recaptcha_check_with_fallback { render :edit } check_repository_error
end end
def show def show
......
...@@ -12,6 +12,8 @@ module Repositories ...@@ -12,6 +12,8 @@ module Repositories
rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422_with_exception
rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_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-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs def info_refs
...@@ -116,6 +118,12 @@ module Repositories ...@@ -116,6 +118,12 @@ module Repositories
def log_user_activity def log_user_activity
Users::ActivityService.new(user).execute Users::ActivityService.new(user).execute
end 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
end end
......
...@@ -64,7 +64,7 @@ class SnippetsController < ApplicationController ...@@ -64,7 +64,7 @@ class SnippetsController < ApplicationController
service_response = Snippets::UpdateService.new(nil, current_user, update_params).execute(@snippet) service_response = Snippets::UpdateService.new(nil, current_user, update_params).execute(@snippet)
@snippet = service_response.payload[:snippet] @snippet = service_response.payload[:snippet]
recaptcha_check_with_fallback { render :edit } check_repository_error
end end
def show def show
......
...@@ -260,12 +260,14 @@ module Issuable ...@@ -260,12 +260,14 @@ module Issuable
highest_priority = highest_label_priority(params).to_sql highest_priority = highest_label_priority(params).to_sql
select_columns = [ # When using CTE make sure to select the same columns that are on the group_by clause.
"#{table_name}.*", # This prevents errors when ignored columns are present in the database.
"(#{highest_priority}) AS highest_priority" issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*"
] + extra_select_columns
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)) .group(issue_grouping_columns(use_cte: with_cte))
.reorder(Gitlab::Database.nulls_last_order('highest_priority', direction)) .reorder(Gitlab::Database.nulls_last_order('highest_priority', direction))
end end
...@@ -301,7 +303,7 @@ module Issuable ...@@ -301,7 +303,7 @@ module Issuable
# Returns an array of arel columns # Returns an array of arel columns
def issue_grouping_columns(use_cte: false) def issue_grouping_columns(use_cte: false)
if use_cte 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 else
arel_table[:id] arel_table[:id]
end end
......
...@@ -56,8 +56,13 @@ class PipelineSerializer < BaseSerializer ...@@ -56,8 +56,13 @@ class PipelineSerializer < BaseSerializer
:manual_actions, :manual_actions,
:scheduled_actions, :scheduled_actions,
:artifacts, :artifacts,
:merge_request,
:user, :user,
{
merge_request: {
source_project: [:route, { namespace: :route }],
target_project: [:route, { namespace: :route }]
}
},
{ {
pending_builds: :project, pending_builds: :project,
project: [:route, { namespace: :route }], project: [:route, { namespace: :route }],
......
...@@ -52,7 +52,7 @@ module Snippets ...@@ -52,7 +52,7 @@ module Snippets
create_commit(snippet) if snippet.repository_exists? create_commit(snippet) if snippet.repository_exists?
end end
rescue rescue
snippet.errors.add(:base, 'Error updating the snippet') snippet.errors.add(:repository, 'Error updating the snippet')
false false
end end
......
---
title: Support finding namespace by ID or path on fork API
merge_request: 20603
author: leoleoasd
type: fixed
---
title: Show git error message updating snippet
merge_request: 26570
author:
type: fixed
---
title: Fix N+1 queries for PipelinesController#index.json
merge_request: 26643
author:
type: performance
...@@ -35,10 +35,14 @@ the SSH configuration of your server by adding the line below to the `/etc/ssh/s ...@@ -35,10 +35,14 @@ the SSH configuration of your server by adding the line below to the `/etc/ssh/s
AcceptEnv GIT_PROTOCOL 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 ```shell
sudo service ssh restart # CentOS 6 / RHEL 6
sudo service sshd restart
# All other supported distributions
sudo systemctl restart ssh
``` ```
## Instructions ## Instructions
......
...@@ -202,14 +202,8 @@ On different cloud vendors a best effort like for like can be used. ...@@ -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 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. 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 [^4]: For data objects such as LFS, Uploads, Artifacts, etc. We recommend a [Cloud Object Storage service](object_storage.md)
where possible over NFS due to better performance and availability. Several types of objects over NFS where possible, due to better performance and availability.
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).
[^5]: NFS can be used as an alternative for both repository data (replacing Gitaly) and [^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 object storage but this isn't typically recommended for performance reasons. Note however it is required for
......
...@@ -736,10 +736,14 @@ To enable the read-only mode: ...@@ -736,10 +736,14 @@ To enable the read-only mode:
This will set the Container Registry into 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 ```sh
# Recycling unused tags
sudo /opt/gitlab/embedded/bin/registry garbage-collect /var/opt/gitlab/registry/config.yml 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. This will start the garbage collection, which might take some time to complete.
......
...@@ -1207,7 +1207,9 @@ POST /projects/:id/fork ...@@ -1207,7 +1207,9 @@ POST /projects/:id/fork
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `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 | | `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 | | `name` | string | no | The name that will be assigned to the resultant project after forking |
......
...@@ -42,7 +42,7 @@ The default image is currently ...@@ -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`. `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, 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 The images used in our pipelines are configured in the
[`gitlab-org/gitlab-build-images`](https://gitlab.com/gitlab-org/gitlab-build-images) [`gitlab-org/gitlab-build-images`](https://gitlab.com/gitlab-org/gitlab-build-images)
...@@ -61,8 +61,8 @@ each pipeline includes default variables defined in ...@@ -61,8 +61,8 @@ each pipeline includes default variables defined in
## Common job definitions ## Common job definitions
Most of the jobs [extend from a few CI definitions](../ci/yaml/README.md#extends) Most of the jobs [extend from a few CI definitions](../ci/yaml/README.md#extends)
that are scoped to a single defined in [`.gitlab/ci/global.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/global.gitlab-ci.yml)
[configuration parameter](../ci/yaml/README.md#configuration-parameters). that are scoped to a single [configuration parameter](../ci/yaml/README.md#configuration-parameters).
| Job definitions | Description | | Job definitions | Description |
|------------------|-------------| |------------------|-------------|
...@@ -72,10 +72,27 @@ that are scoped to a single ...@@ -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. | | `.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-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-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-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-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. | | `.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 ## `rules`, `if:` conditions and `changes:` patterns
We're using the [`rules` keyword](../ci/yaml/README.md#rules) extensively. We're using the [`rules` keyword](../ci/yaml/README.md#rules) extensively.
......
...@@ -331,7 +331,7 @@ metrics: ...@@ -331,7 +331,7 @@ metrics:
unit: "count" 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) ![legend with label shorthand variable](img/prometheus_dashboard_label_variable_shorthand.png)
......
...@@ -12,6 +12,9 @@ module API ...@@ -12,6 +12,9 @@ module API
expose :last_successful_update_at expose :last_successful_update_at
expose :last_error expose :last_error
expose :only_protected_branches expose :only_protected_branches
expose :keep_divergent_refs, if: -> (mirror, _options) do
::Feature.enabled?(:keep_divergent_refs, mirror.project)
end
end end
end end
end end
...@@ -142,6 +142,12 @@ module API ...@@ -142,6 +142,12 @@ module API
end end
end end
def check_namespace_access(namespace)
return namespace if can?(current_user, :read_namespace, namespace)
not_found!('Namespace')
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_namespace(id) def find_namespace(id)
if id.to_s =~ /^\d+$/ if id.to_s =~ /^\d+$/
...@@ -153,13 +159,15 @@ module API ...@@ -153,13 +159,15 @@ module API
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def find_namespace!(id) def find_namespace!(id)
namespace = find_namespace(id) check_namespace_access(find_namespace(id))
end
if can?(current_user, :read_namespace, namespace) def find_namespace_by_path(path)
namespace Namespace.find_by_full_path(path)
else end
not_found!('Namespace')
end def find_namespace_by_path!(path)
check_namespace_access(find_namespace_by_path(path))
end end
def find_branch!(branch_name) def find_branch!(branch_name)
......
...@@ -108,6 +108,10 @@ module API ...@@ -108,6 +108,10 @@ module API
# check_ip - optional, only in EE version, may limit access to # check_ip - optional, only in EE version, may limit access to
# group resources based on its IP restrictions # group resources based on its IP restrictions
post "/allowed" do 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 # It was moved to a separate method so that EE can alter its behaviour more
# easily. # easily.
check_allowed(params) check_allowed(params)
......
...@@ -263,7 +263,9 @@ module API ...@@ -263,7 +263,9 @@ module API
success Entities::Project success Entities::Project
end end
params do 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 :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' optional :name, type: String, desc: 'The name that will be assigned to the fork'
end end
...@@ -273,7 +275,15 @@ module API ...@@ -273,7 +275,15 @@ module API
not_found! unless can?(current_user, :fork_project, user_project) not_found! unless can?(current_user, :fork_project, user_project)
fork_params = declared_params(include_missing: false) 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) service = ::Projects::ForkService.new(user_project, current_user, fork_params)
...@@ -285,8 +295,8 @@ module API ...@@ -285,8 +295,8 @@ module API
conflict!(forked_project.errors.messages) conflict!(forked_project.errors.messages)
else else
present forked_project, with: Entities::Project, present forked_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, forked_project), user_can_admin_project: can?(current_user, :admin_project, forked_project),
current_user: current_user current_user: current_user
end end
end end
......
...@@ -33,9 +33,11 @@ module API ...@@ -33,9 +33,11 @@ module API
requires :url, type: String, desc: 'The URL for a remote mirror' requires :url, type: String, desc: 'The URL for a remote mirror'
optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' 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 :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 end
post ':id/remote_mirrors' do post ':id/remote_mirrors' do
create_params = declared_params(include_missing: false) 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) new_mirror = user_project.remote_mirrors.create(create_params)
...@@ -53,12 +55,15 @@ module API ...@@ -53,12 +55,15 @@ module API
requires :mirror_id, type: String, desc: 'The ID of a remote mirror' requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' 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 :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 end
put ':id/remote_mirrors/:mirror_id' do put ':id/remote_mirrors/:mirror_id' do
mirror = user_project.remote_mirrors.find(params[:mirror_id]) mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror_params = declared_params(include_missing: false) mirror_params = declared_params(include_missing: false)
mirror_params[:id] = mirror_params.delete(:mirror_id) 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 } update_params = { remote_mirrors_attributes: mirror_params }
result = ::Projects::UpdateService result = ::Projects::UpdateService
......
...@@ -8,7 +8,6 @@ module Gitlab ...@@ -8,7 +8,6 @@ module Gitlab
authentication_mechanism: 'The authentication mechanism is not supported.', authentication_mechanism: 'The authentication mechanism is not supported.',
read_snippet: 'You are not allowed to read this snippet.', read_snippet: 'You are not allowed to read this snippet.',
update_snippet: 'You are not allowed to update 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.', 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.' repository_not_found: 'The snippet repository you were looking for could not be found.'
}.freeze }.freeze
...@@ -31,10 +30,6 @@ module Gitlab ...@@ -31,10 +30,6 @@ module Gitlab
raise ForbiddenError, ERROR_MESSAGES[:authentication_mechanism] raise ForbiddenError, ERROR_MESSAGES[:authentication_mechanism]
end end
unless Feature.enabled?(:version_snippets, user)
raise NotFoundError, ERROR_MESSAGES[:project_not_found]
end
check_snippet_accessibility! check_snippet_accessibility!
super super
......
...@@ -38,9 +38,9 @@ describe Projects::PipelinesController do ...@@ -38,9 +38,9 @@ describe Projects::PipelinesController do
expect(response).to match_response_schema('pipeline') expect(response).to match_response_schema('pipeline')
expect(json_response).to include('pipelines') expect(json_response).to include('pipelines')
expect(json_response['pipelines'].count).to eq 5 expect(json_response['pipelines'].count).to eq 6
expect(json_response['count']['all']).to eq '5' expect(json_response['count']['all']).to eq '6'
expect(json_response['count']['running']).to eq '1' expect(json_response['count']['running']).to eq '2'
expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['pending']).to eq '1'
expect(json_response['count']['finished']).to eq '3' expect(json_response['count']['finished']).to eq '3'
...@@ -61,7 +61,7 @@ describe Projects::PipelinesController do ...@@ -61,7 +61,7 @@ describe Projects::PipelinesController do
# There appears to be one extra query for Pipelines#has_warnings? for some reason # 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 { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
expect(response).to have_gitlab_http_status(:ok) 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
end end
...@@ -77,9 +77,9 @@ describe Projects::PipelinesController do ...@@ -77,9 +77,9 @@ describe Projects::PipelinesController do
expect(response).to match_response_schema('pipeline') expect(response).to match_response_schema('pipeline')
expect(json_response).to include('pipelines') expect(json_response).to include('pipelines')
expect(json_response['pipelines'].count).to eq 5 expect(json_response['pipelines'].count).to eq 6
expect(json_response['count']['all']).to eq '5' expect(json_response['count']['all']).to eq '6'
expect(json_response['count']['running']).to eq '1' expect(json_response['count']['running']).to eq '2'
expect(json_response['count']['pending']).to eq '1' expect(json_response['count']['pending']).to eq '1'
expect(json_response['count']['finished']).to eq '3' expect(json_response['count']['finished']).to eq '3'
...@@ -99,8 +99,9 @@ describe Projects::PipelinesController do ...@@ -99,8 +99,9 @@ describe Projects::PipelinesController do
# There appears to be one extra query for Pipelines#has_warnings? for some reason # 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 { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
expect(response).to have_gitlab_http_status(:ok) 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
end end
...@@ -139,7 +140,7 @@ describe Projects::PipelinesController do ...@@ -139,7 +140,7 @@ describe Projects::PipelinesController do
it 'returns the pipelines when the user has access' do it 'returns the pipelines when the user has access' do
get_pipelines_index_json get_pipelines_index_json
expect(json_response['pipelines'].size).to eq(5) expect(json_response['pipelines'].size).to eq(6)
end end
end end
...@@ -155,18 +156,32 @@ describe Projects::PipelinesController do ...@@ -155,18 +156,32 @@ describe Projects::PipelinesController do
%w(pending running success failed canceled).each_with_index do |status, index| %w(pending running success failed canceled).each_with_index do |status, index|
create_pipeline(status, project.commit("HEAD~#{index}")) create_pipeline(status, project.commit("HEAD~#{index}"))
end end
create_pipeline_with_merge_request
end 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) user = create(:user)
pipeline = create(:ci_empty_pipeline, status: status, pipeline = create(:ci_empty_pipeline, status: status,
project: project, project: project,
sha: sha, sha: sha,
user: user) user: user,
merge_request: merge_request)
create_build(pipeline, 'build', 1, 'build', user) create_build(pipeline, 'build', 1, 'build', user)
create_build(pipeline, 'test', 2, 'test', user) create_build(pipeline, 'test', 2, 'test', user)
create_build(pipeline, 'deploy', 3, 'deploy', user) create_build(pipeline, 'deploy', 3, 'deploy', user)
pipeline
end end
def create_build(pipeline, stage, stage_idx, name, user = nil) def create_build(pipeline, stage, stage_idx, name, user = nil)
......
...@@ -135,6 +135,38 @@ describe Repositories::GitHttpController do ...@@ -135,6 +135,38 @@ describe Repositories::GitHttpController do
end end
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 context 'when repository container is a project' do
it_behaves_like 'info_refs behavior' do it_behaves_like 'info_refs behavior' do
let(:user) { project.owner } let(:user) { project.owner }
...@@ -158,6 +190,9 @@ describe Repositories::GitHttpController do ...@@ -158,6 +190,9 @@ describe Repositories::GitHttpController do
let(:expected_class) { Gitlab::GitAccessSnippet } let(:expected_class) { Gitlab::GitAccessSnippet }
let(:expected_object) { personal_snippet } let(:expected_object) { personal_snippet }
end end
it_behaves_like 'snippet feature flag disabled behavior' do
let(:user) { personal_snippet.author }
end
end end
context 'when repository container is a project snippet' do context 'when repository container is a project snippet' do
...@@ -172,5 +207,8 @@ describe Repositories::GitHttpController do ...@@ -172,5 +207,8 @@ describe Repositories::GitHttpController do
let(:expected_class) { Gitlab::GitAccessSnippet } let(:expected_class) { Gitlab::GitAccessSnippet }
let(:expected_object) { project_snippet } let(:expected_object) { project_snippet }
end end
it_behaves_like 'snippet feature flag disabled behavior' do
let(:user) { project_snippet.author }
end
end end
end end
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
describe 'Projects > Snippets > User updates a snippet' do 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!(:snippet) { create(:project_snippet, project: project, author: user) }
let(:user) { create(:user) }
before do before do
stub_feature_flags(snippets_vue: false) stub_feature_flags(snippets_vue: false)
...@@ -13,16 +13,33 @@ describe 'Projects > Snippets > User updates a snippet' do ...@@ -13,16 +13,33 @@ describe 'Projects > Snippets > User updates a snippet' do
sign_in(user) sign_in(user)
visit(project_snippet_path(project, snippet)) visit(project_snippet_path(project, snippet))
end
it 'updates a snippet' do
page.within('.detail-page-header') do page.within('.detail-page-header') do
first(:link, 'Edit').click first(:link, 'Edit').click
end end
end
it 'updates a snippet' do
fill_in('project_snippet_title', with: 'Snippet new title') fill_in('project_snippet_title', with: 'Snippet new title')
click_button('Save') click_button('Save')
expect(page).to have_content('Snippet new title') expect(page).to have_content('Snippet new title')
end 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 end
...@@ -8,7 +8,7 @@ describe 'User edits snippet', :js do ...@@ -8,7 +8,7 @@ describe 'User edits snippet', :js do
let(:file_name) { 'test.rb' } let(:file_name) { 'test.rb' }
let(:content) { 'puts "test"' } 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) } let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
before do before do
...@@ -58,4 +58,21 @@ describe 'User edits snippet', :js 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_no_xpath("//i[@class='fa fa-lock']")
expect(page).to have_xpath("//i[@class='fa fa-globe']") expect(page).to have_xpath("//i[@class='fa fa-globe']")
end 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 end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"last_successful_update_at", "last_successful_update_at",
"last_error", "last_error",
"only_protected_branches" "only_protected_branches"
], ],
"properties": { "properties": {
"id": { "type": "integer" }, "id": { "type": "integer" },
"enabled": { "type": "boolean" }, "enabled": { "type": "boolean" },
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
"last_update_started_at": { "type": ["string", "null"] }, "last_update_started_at": { "type": ["string", "null"] },
"last_successful_update_at": { "type": ["string", "null"] }, "last_successful_update_at": { "type": ["string", "null"] },
"last_error": { "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 "additionalProperties": false
} }
...@@ -31,12 +31,15 @@ describe Gitlab::GitAccessSnippet do ...@@ -31,12 +31,15 @@ describe Gitlab::GitAccessSnippet do
end end
describe 'when feature flag :version_snippets is disabled' do describe 'when feature flag :version_snippets is disabled' do
let(:user) { snippet.author }
before do before do
stub_feature_flags(version_snippets: false) stub_feature_flags(version_snippets: false)
end end
it 'does not allow push and pull access' do it 'allows push and pull access' do
expect { pull_access_check }.to raise_project_not_found expect { pull_access_check }.not_to raise_error
expect { push_access_check }.not_to raise_error
end end
end end
......
...@@ -315,6 +315,18 @@ describe API::Internal::Base do ...@@ -315,6 +315,18 @@ describe API::Internal::Base do
end end
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 context 'git push with personal snippet' do
it 'responds with success' do it 'responds with success' do
push(key, personal_snippet) push(key, personal_snippet)
...@@ -325,6 +337,10 @@ describe API::Internal::Base do ...@@ -325,6 +337,10 @@ describe API::Internal::Base do
expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}")
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
it_behaves_like 'snippets with disabled feature flag' do
subject { push(key, personal_snippet) }
end
end end
context 'git pull with personal snippet' do context 'git pull with personal snippet' do
...@@ -337,6 +353,10 @@ describe API::Internal::Base do ...@@ -337,6 +353,10 @@ describe API::Internal::Base do
expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}") expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}")
expect(user.reload.last_activity_on).to eql(Date.today) expect(user.reload.last_activity_on).to eql(Date.today)
end end
it_behaves_like 'snippets with disabled feature flag' do
subject { pull(key, personal_snippet) }
end
end end
context 'git push with project snippet' do context 'git push with project snippet' do
...@@ -349,6 +369,10 @@ describe API::Internal::Base do ...@@ -349,6 +369,10 @@ describe API::Internal::Base do
expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}")
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
it_behaves_like 'snippets with disabled feature flag' do
subject { push(key, project_snippet) }
end
end end
context 'git pull with project snippet' do context 'git pull with project snippet' do
...@@ -361,6 +385,10 @@ describe API::Internal::Base do ...@@ -361,6 +385,10 @@ describe API::Internal::Base do
expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}") expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}")
expect(user.reload.last_activity_on).to eql(Date.today) expect(user.reload.last_activity_on).to eql(Date.today)
end end
it_behaves_like 'snippets with disabled feature flag' do
subject { pull(key, project_snippet) }
end
end end
context "git pull" do context "git pull" do
......
...@@ -2871,6 +2871,66 @@ describe API::Projects do ...@@ -2871,6 +2871,66 @@ describe API::Projects do
expect(json_response['namespace']['name']).to eq(group2.name) expect(json_response['namespace']['name']).to eq(group2.name)
end 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 it 'forks to owned subgroup' do
full_path = "#{group2.path}/#{group3.path}" full_path = "#{group2.path}/#{group3.path}"
post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path } post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path }
......
...@@ -91,6 +91,10 @@ describe API::RemoteMirrors do ...@@ -91,6 +91,10 @@ describe API::RemoteMirrors do
let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } } let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } }
let(:mirror) { project.remote_mirrors.first } let(:mirror) { project.remote_mirrors.first }
before do
stub_feature_flags(keep_divergent_refs: false)
end
it 'requires `admin_remote_mirror` permission' do it 'requires `admin_remote_mirror` permission' do
put api(route[mirror.id], developer) put api(route[mirror.id], developer)
...@@ -102,12 +106,31 @@ describe API::RemoteMirrors do ...@@ -102,12 +106,31 @@ describe API::RemoteMirrors do
put api(route[mirror.id], user), params: { put api(route[mirror.id], user), params: {
enabled: '0', enabled: '0',
only_protected_branches: 'true' only_protected_branches: 'true',
keep_divergent_refs: 'true'
} }
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(json_response['enabled']).to eq(false) expect(json_response['enabled']).to eq(false)
expect(json_response['only_protected_branches']).to eq(true) 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 end
# TODO: Remove flag: https://gitlab.com/gitlab-org/gitlab/issues/38121 # TODO: Remove flag: https://gitlab.com/gitlab-org/gitlab/issues/38121
......
...@@ -148,7 +148,7 @@ describe Snippets::UpdateService do ...@@ -148,7 +148,7 @@ describe Snippets::UpdateService do
response = subject response = subject
expect(response).to be_error 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
end end
...@@ -173,7 +173,7 @@ describe Snippets::UpdateService do ...@@ -173,7 +173,7 @@ describe Snippets::UpdateService do
response = subject response = subject
expect(response).to be_error 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
it 'returns error if snippet does not have a snippet_repository' do it 'returns error if snippet does not have a snippet_repository' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment