summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-08-26 10:55:20 +0200
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-08-26 10:55:20 +0200
commit15ace6a910ecb889c58fd4838a4f9a9b710178c7 (patch)
tree647da348e903d9acb86f6063e49d958f207ba874
parente984a8a3543fa9a26308cea35a42046392f4a488 (diff)
parent2be34630623711fc20ef8c101b5cef688f207cc1 (diff)
downloadgitlab-ce-15ace6a910ecb889c58fd4838a4f9a9b710178c7.tar.gz
Merge commit '2be34630623711fc20ef8c101b5cef688f207cc1' into backstage/gb/rename-ci-cd-processing-sidekiq-queuesbackstage/gb/rename-ci-cd-processing-sidekiq-queues
* commit '2be34630623711fc20ef8c101b5cef688f207cc1': Common Docker Documentation Location in `gitlab-ce` fix deprecation warning present during webpack compiles Enable 5 lines of Sidekiq backtrace lines to aid in debugging Add support for copying permalink to notes via more actions dropdown Handle creating a nested group on MySQL correctly Decrease statuses batch size even more in a migration Fix repo editor scrollbar Replace 'source/search_code.feature' spinach test with an rspec analog Authorizations regarding OAuth - style confirmation Update README.md Refactor complicated API group finding rules into GroupsFinder Fix group and project search for anonymous users Document version Group Milestones API introduced Allow v4 API GET requests for groups to be unauthenticated Adjust a range and a size in stages statuses migration Update README.md Point to /developers on docs/administration/authentiq.md Indexes GFM markdown guide use inline links instead of referenced Add index on ci_runners.contacted_at
-rw-r--r--app/assets/javascripts/api.js15
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js14
-rw-r--r--app/assets/stylesheets/framework/modal.scss8
-rw-r--r--app/assets/stylesheets/pages/repo.scss1
-rw-r--r--app/finders/groups_finder.rb36
-rw-r--r--app/helpers/button_helper.rb20
-rw-r--r--app/services/groups/nested_create_service.rb4
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml78
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml2
-rw-r--r--app/views/search/_form.html.haml2
-rw-r--r--app/workers/concerns/exception_backtrace.rb8
-rw-r--r--app/workers/group_destroy_worker.rb1
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb1
-rw-r--r--app/workers/project_destroy_worker.rb1
-rw-r--r--app/workers/project_export_worker.rb1
-rw-r--r--app/workers/repository_import_worker.rb1
-rw-r--r--changelogs/unreleased/31409-fix-group-and-project-search-for-anonymous-users.yml5
-rw-r--r--changelogs/unreleased/35721-auth-style-confirmation.yml5
-rw-r--r--changelogs/unreleased/35811-copy-link-note.yml5
-rw-r--r--changelogs/unreleased/docs-document-version-for-group-milestones-api.yml5
-rw-r--r--changelogs/unreleased/replace_spinach_search_code-feature.yml5
-rw-r--r--config/webpack.config.js2
-rw-r--r--db/post_migrate/20170711145558_migrate_stages_statuses.rb8
-rw-r--r--doc/administration/auth/authentiq.md2
-rw-r--r--doc/api/group_milestones.md5
-rw-r--r--doc/api/groups.md9
-rw-r--r--doc/ci/variables/README.md5
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/doc_styleguide.md13
-rw-r--r--doc/install/README.md2
-rw-r--r--doc/install/docker.md18
-rw-r--r--doc/user/index.md7
-rw-r--r--docker/README.md6
-rw-r--r--features/project/source/search_code.feature15
-rw-r--r--features/steps/project/source/search_code.rb19
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/api/groups.rb14
-rw-r--r--spec/features/projects/files/find_files_spec.rb23
-rw-r--r--spec/features/projects/files/user_searches_for_files_spec.rb58
-rw-r--r--spec/features/search_spec.rb26
-rw-r--r--spec/helpers/button_helper_spec.rb63
-rw-r--r--spec/javascripts/api_spec.js26
-rw-r--r--spec/javascripts/project_title_spec.js6
-rw-r--r--spec/lib/gitlab/bare_repository_importer_spec.rb104
-rw-r--r--spec/migrations/migrate_stages_statuses_spec.rb7
-rw-r--r--spec/requests/api/groups_spec.rb21
-rw-r--r--spec/services/groups/nested_create_service_spec.rb87
47 files changed, 543 insertions, 227 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 4319bfcc57f..78cb3def879 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -55,13 +55,18 @@ const Api = {
// Return projects list. Filtered by query
projects(query, options, callback) {
const url = Api.buildUrl(Api.projectsPath);
+ const defaults = {
+ search: query,
+ per_page: 20,
+ };
+
+ if (gon.current_user_id) {
+ defaults.membership = true;
+ }
+
return $.ajax({
url,
- data: Object.assign({
- search: query,
- per_page: 20,
- membership: true,
- }, options),
+ data: Object.assign(defaults, options),
dataType: 'json',
})
.done(projects => callback(projects));
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index ab9a8e43dd1..1f3c7e1772d 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -29,12 +29,14 @@ showTooltip = function(target, title) {
var $target = $(target);
var originalTitle = $target.data('original-title');
- $target
- .attr('title', 'Copied')
- .tooltip('fixTitle')
- .tooltip('show')
- .attr('title', originalTitle)
- .tooltip('fixTitle');
+ if (!$target.data('hideTooltip')) {
+ $target
+ .attr('title', 'Copied')
+ .tooltip('fixTitle')
+ .tooltip('show')
+ .attr('title', originalTitle)
+ .tooltip('fixTitle');
+ }
};
$(function() {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index d1f00d3ee2c..5b581780447 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -16,6 +16,14 @@ body.modal-open {
overflow: hidden;
}
+.modal-no-backdrop {
+ @extend .modal-dialog;
+
+ .modal-content {
+ box-shadow: none;
+ }
+}
+
@media (min-width: $screen-md-min) {
.modal-dialog {
width: 860px;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 37971d6bd3a..1088eca5322 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -182,7 +182,6 @@
padding: 5px 10px;
position: relative;
border-top: 1px solid $white-normal;
- margin-top: -5px;
}
#binary-viewer {
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index e6fb112e7f2..88d71b0a87b 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -1,3 +1,19 @@
+# GroupsFinder
+#
+# Used to filter Groups by a set of params
+#
+# Arguments:
+# current_user - which user is requesting groups
+# params:
+# owned: boolean
+# parent: Group
+# all_available: boolean (defaults to true)
+#
+# Users with full private access can see all groups. The `owned` and `parent`
+# params can be used to restrict the groups that are returned.
+#
+# Anonymous users will never return any `owned` groups. They will return all
+# public groups instead, even if `all_available` is set to false.
class GroupsFinder < UnionFinder
def initialize(current_user = nil, params = {})
@current_user = current_user
@@ -16,13 +32,13 @@ class GroupsFinder < UnionFinder
attr_reader :current_user, :params
def all_groups
- groups = []
-
- if current_user
- groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups
- end
- groups << Group.unscoped.public_to_user(current_user)
+ return [owned_groups] if params[:owned]
+ return [Group.all] if current_user&.full_private_access?
+ groups = []
+ groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
+ groups << Group.unscoped.public_to_user(current_user) if include_public_groups?
+ groups << Group.none if groups.empty?
groups
end
@@ -39,4 +55,12 @@ class GroupsFinder < UnionFinder
groups.where(parent: params[:parent])
end
+
+ def owned_groups
+ current_user&.groups || Group.none
+ end
+
+ def include_public_groups?
+ current_user.nil? || params.fetch(:all_available, true)
+ end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index bf9ad95b7c2..48cf30a48ab 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -20,6 +20,9 @@ module ButtonHelper
def clipboard_button(data = {})
css_class = data[:class] || 'btn-clipboard btn-transparent'
title = data[:title] || 'Copy to clipboard'
+ button_text = data[:button_text] || ''
+ hide_tooltip = data[:hide_tooltip] || false
+ hide_button_icon = data[:hide_button_icon] || false
# This supports code in app/assets/javascripts/copy_to_clipboard.js that
# works around ClipboardJS limitations to allow the context-specific copy/pasting of plain text or GFM.
@@ -35,17 +38,22 @@ module ButtonHelper
target = data.delete(:target)
data[:clipboard_target] = target if target
- data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
+ unless hide_tooltip
+ data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
+ end
- content_tag :button,
- icon('clipboard', 'aria-hidden': 'true'),
+ button_attributes = {
class: "btn #{css_class}",
data: data,
type: :button,
title: title,
- aria: {
- label: title
- }
+ aria: { label: title }
+ }
+
+ content_tag :button, button_attributes do
+ concat(icon('clipboard', 'aria-hidden': 'true')) unless hide_button_icon
+ concat(button_text)
+ end
end
def http_clone_button(project, placement = 'right', append_link: true)
diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index 8d793f5c02e..d6f08fc3cce 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -15,6 +15,10 @@ module Groups
return group
end
+ if group_path.include?('/') && !Group.supports_nested_groups?
+ raise 'Nested groups are not supported on MySQL'
+ end
+
create_group_path
end
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 82aa51f9778..8ba88906714 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -1,39 +1,43 @@
-%h3.page-title Authorization required
%main{ :role => "main" }
- %p.h4
- Authorize
- %strong.text-info= @pre_auth.client.name
- to use your account?
+ .modal-no-backdrop
+ .modal-content
+ .modal-header
+ %h3.page-title
+ Authorize
+ = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
+ to use your account?
- - if current_user.admin?
- .text-warning.prepend-top-20
- %p
- = icon("exclamation-triangle fw")
- You are an admin, which means granting access to
- %strong= @pre_auth.client.name
- will allow them to interact with GitLab as an admin as well. Proceed with caution.
-
- - if @pre_auth.scopes
- #oauth-permissions
- %p This application will be able to:
- %ul.text-info
- - @pre_auth.scopes.each do |scope|
- %li= t scope, scope: [:doorkeeper, :scopes]
- %hr/
- .actions
- = form_tag oauth_authorization_path, method: :post do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag "Authorize", class: "btn btn-success wide pull-left"
- = form_tag oauth_authorization_path, method: :delete do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
+ .modal-body
+ - if current_user.admin?
+ .text-warning
+ %p
+ = icon("exclamation-triangle fw")
+ You are an admin, which means granting access to
+ %strong= @pre_auth.client.name
+ will allow them to interact with GitLab as an admin as well. Proceed with caution.
+ %p
+ You are about to authorize
+ = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer'
+ to use your account.
+ - if @pre_auth.scopes
+ This application will be able to:
+ %ul
+ - @pre_auth.scopes.each do |scope|
+ %li= t scope, scope: [:doorkeeper, :scopes]
+ .form-actions.text-right
+ = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
+ = submit_tag "Deny", class: "btn btn-danger"
+ = form_tag oauth_authorization_path, method: :post, class: 'inline' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
+ = submit_tag "Authorize", class: "btn btn-success prepend-left-10"
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 5930209a682..7e854186973 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -6,6 +6,8 @@
%span.icon
= custom_icon('ellipsis_v')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
+ %li
+ = clipboard_button(text: noteable_note_url(note), title: "Copy reference to clipboard", button_text: 'Copy link', hide_tooltip: true, hide_button_icon: true)
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 3139be1cd37..a4a5cec1314 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -11,5 +11,5 @@
%span.sr-only
Clear search
- unless params[:snippets].eql? 'true'
- = render 'filter' if current_user
+ = render 'filter'
= button_tag "Search", class: "btn btn-success btn-search"
diff --git a/app/workers/concerns/exception_backtrace.rb b/app/workers/concerns/exception_backtrace.rb
new file mode 100644
index 00000000000..ea0f1f8d19b
--- /dev/null
+++ b/app/workers/concerns/exception_backtrace.rb
@@ -0,0 +1,8 @@
+# Concern for enabling a few lines of exception backtraces in Sidekiq
+module ExceptionBacktrace
+ extend ActiveSupport::Concern
+
+ included do
+ sidekiq_options backtrace: 5
+ end
+end
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
index 07e82767b06..bd8e212e928 100644
--- a/app/workers/group_destroy_worker.rb
+++ b/app/workers/group_destroy_worker.rb
@@ -1,6 +1,7 @@
class GroupDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ include ExceptionBacktrace
def perform(group_id, user_id)
begin
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index 1cfb0be759e..f1cd1769421 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -7,6 +7,7 @@
class NamespacelessProjectDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ include ExceptionBacktrace
def self.bulk_perform_async(args_list)
Sidekiq::Client.push_bulk('class' => self, 'queue' => sidekiq_options['queue'], 'args' => args_list)
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index a9188b78460..3be7e686609 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -1,6 +1,7 @@
class ProjectDestroyWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ include ExceptionBacktrace
def perform(project_id, user_id, params)
project = Project.find(project_id)
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index 6009aa1b191..f13ac9e5db2 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -1,6 +1,7 @@
class ProjectExportWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ include ExceptionBacktrace
sidekiq_options retry: 3
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 2c2d1e8b91f..00a021abbdc 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -3,6 +3,7 @@ class RepositoryImportWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ include ExceptionBacktrace
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
diff --git a/changelogs/unreleased/31409-fix-group-and-project-search-for-anonymous-users.yml b/changelogs/unreleased/31409-fix-group-and-project-search-for-anonymous-users.yml
new file mode 100644
index 00000000000..06e8180db64
--- /dev/null
+++ b/changelogs/unreleased/31409-fix-group-and-project-search-for-anonymous-users.yml
@@ -0,0 +1,5 @@
+---
+title: Fix group and project search for anonymous users
+merge_request: 13745
+author:
+type: fixed
diff --git a/changelogs/unreleased/35721-auth-style-confirmation.yml b/changelogs/unreleased/35721-auth-style-confirmation.yml
new file mode 100644
index 00000000000..9963f76e845
--- /dev/null
+++ b/changelogs/unreleased/35721-auth-style-confirmation.yml
@@ -0,0 +1,5 @@
+---
+title: restyling of OAuth authorization confirmation
+merge_request:
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/35811-copy-link-note.yml b/changelogs/unreleased/35811-copy-link-note.yml
new file mode 100644
index 00000000000..9fa74884c8a
--- /dev/null
+++ b/changelogs/unreleased/35811-copy-link-note.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for copying permalink to notes via more actions dropdown
+merge_request: 13299
+author:
+type: added
diff --git a/changelogs/unreleased/docs-document-version-for-group-milestones-api.yml b/changelogs/unreleased/docs-document-version-for-group-milestones-api.yml
new file mode 100644
index 00000000000..d75c46313f4
--- /dev/null
+++ b/changelogs/unreleased/docs-document-version-for-group-milestones-api.yml
@@ -0,0 +1,5 @@
+---
+title: Document version Group Milestones API introduced
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/replace_spinach_search_code-feature.yml b/changelogs/unreleased/replace_spinach_search_code-feature.yml
new file mode 100644
index 00000000000..28d2108c871
--- /dev/null
+++ b/changelogs/unreleased/replace_spinach_search_code-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace 'source/search_code.feature' spinach test with an rspec analog
+merge_request: 13697
+author: blackst0ne
+type: other
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 8aa938d538e..7d63a42d7d8 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -170,7 +170,7 @@ var config = {
if (chunk.name) {
return chunk.name;
}
- return chunk.modules.map((m) => {
+ return chunk.mapModules((m) => {
var chunkPath = m.request.split('!').pop();
return path.relative(m.context, chunkPath);
}).join('_');
diff --git a/db/post_migrate/20170711145558_migrate_stages_statuses.rb b/db/post_migrate/20170711145558_migrate_stages_statuses.rb
index 5a24fb1307f..aeb900354db 100644
--- a/db/post_migrate/20170711145558_migrate_stages_statuses.rb
+++ b/db/post_migrate/20170711145558_migrate_stages_statuses.rb
@@ -6,7 +6,7 @@ class MigrateStagesStatuses < ActiveRecord::Migration
disable_ddl_transaction!
BATCH_SIZE = 10000
- RANGE_SIZE = 1000
+ RANGE_SIZE = 100
MIGRATION = 'MigrateStageStatus'.freeze
class Stage < ActiveRecord::Base
@@ -17,10 +17,10 @@ class MigrateStagesStatuses < ActiveRecord::Migration
def up
Stage.where(status: nil).each_batch(of: BATCH_SIZE) do |relation, index|
relation.each_batch(of: RANGE_SIZE) do |batch|
- range = relation.pluck('MIN(id)', 'MAX(id)').first
- schedule = index * 5.minutes
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+ delay = index * 5.minutes
- BackgroundMigrationWorker.perform_in(schedule, MIGRATION, range)
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, range)
end
end
end
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index 1528f1d2b17..252ff1f4b15 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -4,7 +4,7 @@ To enable the Authentiq OmniAuth provider for passwordless authentication you mu
Authentiq will generate a Client ID and the accompanying Client Secret for you to use.
-1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/register).
+1. Get your Client credentials (Client ID and Client Secret) at [Authentiq](https://www.authentiq.com/developers).
2. On your GitLab server, open the configuration file:
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index dbfc7529125..a96fb3124fc 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -1,5 +1,8 @@
# Group milestones API
+> **Notes:**
+> [Introduced][ce-12819] in GitLab 9.5.
+
## List group milestones
Returns a list of group milestones.
@@ -118,3 +121,5 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a group milestone
+
+[ce-12819]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12819
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 2b3d8e125c8..c2daa8bc029 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -2,7 +2,8 @@
## List groups
-Get a list of groups. (As user: my groups or all available, as admin: all groups).
+Get a list of visible groups for the authenticated user. When accessed without
+authentication, only public groups are returned.
Parameters:
@@ -43,7 +44,8 @@ You can search for groups by name or path, see below.
## List a group's projects
-Get a list of projects in this group.
+Get a list of projects in this group. When accessed without authentication, only
+public projects are returned.
```
GET /groups/:id/projects
@@ -109,7 +111,8 @@ Example response:
## Details of a group
-Get all details of a group.
+Get all details of a group. This endpoint can be accessed without authentication
+if the group is publicly accessible.
```
GET /groups/:id
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index e55a92dbb71..234dc530db0 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -86,6 +86,11 @@ To follow conventions of naming across GitLab, and to futher move away from the
`build` term and toward `job` CI variables have been renamed for the 9.0
release.
+>**Note:**
+Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
+strongly advised to use the new variables as we will remove the old ones in
+future GitLab releases.**
+
| 8.x name | 9.0+ name |
| --------------------- |------------------------ |
| `CI_BUILD_ID` | `CI_JOB_ID` |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 1869782fe6e..abf4ec7dbf8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1065,6 +1065,8 @@ a list of all previous jobs from which the artifacts should be downloaded.
You can only define jobs from stages that are executed before the current one.
An error will be shown if you define jobs from the current stage or next ones.
Defining an empty array will skip downloading any artifacts for that job.
+The status of the previous job is not considered when using `dependencies`, so
+if it failed or it is a manual job that was not run, no error occurs.
---
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 90d1d9657b9..798f40eef3d 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -113,13 +113,12 @@ merge request.
## Links
-- If a link makes the paragraph to span across multiple lines, do not use
- the regular Markdown approach: `[Text](https://example.com)`. Instead use
- `[Text][identifier]` and at the very bottom of the document add:
- `[identifier]: https://example.com`. This is another way to create Markdown
- links which keeps the document clear and concise. Bonus points if you also
- add an alternative text: `[identifier]: https://example.com "Alternative text"`
- that appears when hovering your mouse on a link
+- Use the regular inline link markdown markup `[Text](https://example.com)`.
+ It's easier to read, review, and maintain.
+- If there's a link that repeats several times through the same document,
+ you can use `[Text][identifier]` and at the bottom of the section or the
+ document add: `[identifier]: https://example.com`, in which case, we do
+ encourage you to also add an alternative text: `[identifier]: https://example.com "Alternative text"` that appears when hovering your mouse on a link.
### Linking to inline docs
diff --git a/doc/install/README.md b/doc/install/README.md
index 1d510cb29c3..656f8720361 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -17,7 +17,7 @@ the hardware requirements.
- [Installation from source](installation.md) - Install GitLab from source.
Useful for unsupported systems like *BSD. For an overview of the directory
structure, read the [structure documentation](structure.md).
-- [Docker](https://docs.gitlab.com/omnibus/docker/) - Install GitLab using Docker.
+- [Docker](docker.md) - Install GitLab using Docker.
## Install GitLab on cloud providers
diff --git a/doc/install/docker.md b/doc/install/docker.md
new file mode 100644
index 00000000000..933756072ff
--- /dev/null
+++ b/doc/install/docker.md
@@ -0,0 +1,18 @@
+# GitLab Docker images
+
+[Docker](https://www.docker.com) and container technology have been revolutionizing the software world for the past few years. They combine the performance and efficiency of native execution with the abstraction, security, and immutability of virtualization.
+
+GitLab provides official Docker images to allowing you to easily take advantage of the benefits of containerization while operating your GitLab instance.
+
+## Omnibus GitLab based images
+
+GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
+* [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
+* [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/)
+* [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/)
+
+A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
+
+## Cloud native images
+
+GitLab is also working towards a [cloud native set of containers](https://gitlab.com/charts/helm.gitlab.io#docker-container-images), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
diff --git a/doc/user/index.md b/doc/user/index.md
index e9ec603f2f1..f239a15d441 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -119,6 +119,13 @@ When performing inline reviews to implementations
to your codebase through merge requests you can
gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
+### GitLab Flavored Markdown (GFM)
+
+Read through the [GFM documentation](markdown.md) to learn how to apply
+the best of GitLab Flavored Markdown in your discussions, comments,
+issues and merge requests descriptions, and everywhere else GMF is
+supported.
+
## Todos
Never forget to reply to your collaborators. [GitLab Todos](../workflow/todos.md)
diff --git a/docker/README.md b/docker/README.md
index f9e12c5733b..61b41d2f109 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,7 +1,3 @@
# GitLab Docker images
-* The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/).
-* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/).
-* The complete usage guide can be found in [Using GitLab Docker images](https://docs.gitlab.com/omnibus/docker/)
-* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
-* Check the guide for [creating Omnibus-based Docker Image](https://docs.gitlab.com/omnibus/build/README.html#build-docker-image)
+This content has been moved to [our documentation site](https://docs.gitlab.com/ce/install/docker.html).
diff --git a/features/project/source/search_code.feature b/features/project/source/search_code.feature
deleted file mode 100644
index 4f9dcea249f..00000000000
--- a/features/project/source/search_code.feature
+++ /dev/null
@@ -1,15 +0,0 @@
-Feature: Project Source Search Code
- Background:
- Given I sign in as a user
-
- Scenario: Search for term "coffee"
- Given I own project "Shop"
- And I visit project source page
- When I search for term "coffee"
- Then I should see files from repository containing "coffee"
-
- Scenario: Search on empty project
- Given I own an empty project
- And I visit my project's home page
- When I search for term "coffee"
- Then I should see empty result
diff --git a/features/steps/project/source/search_code.rb b/features/steps/project/source/search_code.rb
deleted file mode 100644
index feee756d7ec..00000000000
--- a/features/steps/project/source/search_code.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'I search for term "coffee"' do
- fill_in "search", with: "coffee"
- click_button "Go"
- end
-
- step 'I should see files from repository containing "coffee"' do
- expect(page).to have_content 'coffee'
- expect(page).to have_content 'CONTRIBUTING.md'
- end
-
- step 'I should see empty result' do
- expect(page).to have_content "We couldn't find any"
- end
-end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 830263fd038..be69a96c3ee 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -304,10 +304,6 @@ module SharedPaths
visit project_commits_path(@project, 'stable', { limit: 5 })
end
- step 'I visit project source page' do
- visit project_tree_path(@project, root_ref)
- end
-
step 'I visit blob file from repo' do
visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 49c3b2278c7..e56427304a6 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -2,7 +2,7 @@ module API
class Groups < Grape::API
include PaginationParams
- before { authenticate! }
+ before { authenticate_non_get! }
helpers do
params :optional_params_ce do
@@ -47,16 +47,8 @@ module API
use :pagination
end
get do
- groups = if params[:owned]
- current_user.owned_groups
- elsif current_user.admin
- Group.all
- elsif params[:all_available]
- GroupsFinder.new(current_user).execute
- else
- current_user.groups
- end
-
+ find_params = { all_available: params[:all_available], owned: params[:owned] }
+ groups = GroupsFinder.new(current_user, find_params).execute
groups = groups.search(params[:search]) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort])
diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb
deleted file mode 100644
index 57d67b28920..00000000000
--- a/spec/features/projects/files/find_files_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-feature 'Find files button in the tree header' do
- given(:user) { create(:user) }
- given(:project) { create(:project, :repository) }
-
- background do
- sign_in(user)
- project.team << [user, :developer]
- end
-
- scenario 'project main screen' do
- visit project_path(project)
-
- expect(page).to have_selector('.tree-controls .shortcuts-find-file')
- end
-
- scenario 'project tree screen' do
- visit project_tree_path(project, project.default_branch)
-
- expect(page).to have_selector('.tree-controls .shortcuts-find-file')
- end
-end
diff --git a/spec/features/projects/files/user_searches_for_files_spec.rb b/spec/features/projects/files/user_searches_for_files_spec.rb
new file mode 100644
index 00000000000..a105685bca7
--- /dev/null
+++ b/spec/features/projects/files/user_searches_for_files_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe 'User searches for files' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'project main screen' do
+ context 'when project is empty' do
+ let(:empty_project) { create(:project) }
+
+ before do
+ empty_project.add_developer(user)
+ visit project_path(empty_project)
+ end
+
+ it 'does not show any result' do
+ fill_in('search', with: 'coffee')
+ click_button('Go')
+
+ expect(page).to have_content("We couldn't find any")
+ end
+ end
+
+ context 'when project is not empty' do
+ before do
+ project.add_developer(user)
+ visit project_path(project)
+ end
+
+ it 'shows "Find file" button' do
+ expect(page).to have_selector('.tree-controls .shortcuts-find-file')
+ end
+ end
+ end
+
+ describe 'project tree screen' do
+ before do
+ project.add_developer(user)
+ visit project_tree_path(project, project.default_branch)
+ end
+
+ it 'shows "Find file" button' do
+ expect(page).to have_selector('.tree-controls .shortcuts-find-file')
+ end
+
+ it 'shows found files' do
+ fill_in('search', with: 'coffee')
+ click_button('Go')
+
+ expect(page).to have_content('coffee')
+ expect(page).to have_content('CONTRIBUTING.md')
+ end
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 6742d77937f..31d509455ba 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -281,4 +281,30 @@ describe "Search" do
expect(page).to have_selector('.commit-row-description', count: 9)
end
end
+
+ context 'anonymous user' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ sign_out(user)
+ end
+
+ it 'preserves the group being searched in' do
+ visit search_path(group_id: project.namespace.id)
+
+ fill_in 'search', with: 'foo'
+ click_button 'Search'
+
+ expect(find('#group_id').value).to eq(project.namespace.id.to_s)
+ end
+
+ it 'preserves the project being searched in' do
+ visit search_path(project_id: project.id)
+
+ fill_in 'search', with: 'foo'
+ click_button 'Search'
+
+ expect(find('#project_id').value).to eq(project.id.to_s)
+ end
+ end
end
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 250ba239033..4423560ecaa 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -62,4 +62,67 @@ describe ButtonHelper do
end
end
end
+
+ describe 'clipboard_button' do
+ let(:user) { create(:user) }
+ let(:project) { build_stubbed(:project) }
+
+ def element(data = {})
+ element = helper.clipboard_button(data)
+ Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
+ end
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'with default options' do
+ context 'when no `text` attribute is not provided' do
+ it 'shows copy to clipboard button with default configuration and no text set to copy' do
+ expect(element.attr('class')).to eq('btn btn-clipboard btn-transparent')
+ expect(element.attr('type')).to eq('button')
+ expect(element.attr('aria-label')).to eq('Copy to clipboard')
+ expect(element.attr('data-toggle')).to eq('tooltip')
+ expect(element.attr('data-placement')).to eq('bottom')
+ expect(element.attr('data-container')).to eq('body')
+ expect(element.attr('data-clipboard-text')).to eq(nil)
+ expect(element.inner_text).to eq("")
+
+ expect(element).to have_selector('.fa.fa-clipboard')
+ end
+ end
+
+ context 'when `text` attribute is provided' do
+ it 'shows copy to clipboard button with provided `text` to copy' do
+ expect(element(text: 'Hello World!').attr('data-clipboard-text')).to eq('Hello World!')
+ end
+ end
+
+ context 'when `title` attribute is provided' do
+ it 'shows copy to clipboard button with provided `title` as tooltip' do
+ expect(element(title: 'Copy to my clipboard!').attr('aria-label')).to eq('Copy to my clipboard!')
+ end
+ end
+ end
+
+ context 'with `button_text` attribute provided' do
+ it 'shows copy to clipboard button with provided `button_text` as button label' do
+ expect(element(button_text: 'Copy text').inner_text).to eq('Copy text')
+ end
+ end
+
+ context 'with `hide_tooltip` attribute provided' do
+ it 'shows copy to clipboard button without tooltip support' do
+ expect(element(hide_tooltip: true).attr('data-placement')).to eq(nil)
+ expect(element(hide_tooltip: true).attr('data-toggle')).to eq(nil)
+ expect(element(hide_tooltip: true).attr('data-container')).to eq(nil)
+ end
+ end
+
+ context 'with `hide_button_icon` attribute provided' do
+ it 'shows copy to clipboard button without tooltip support' do
+ expect(element(hide_button_icon: true)).not_to have_selector('.fa.fa-clipboard')
+ end
+ end
+ end
end
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 867322ce8ae..8c68ceff914 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -17,7 +17,7 @@ describe('Api', () => {
beforeEach(() => {
originalGon = window.gon;
- window.gon = dummyGon;
+ window.gon = Object.assign({}, dummyGon);
});
afterEach(() => {
@@ -98,10 +98,11 @@ describe('Api', () => {
});
describe('projects', () => {
- it('fetches projects', (done) => {
+ it('fetches projects with membership when logged in', (done) => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ window.gon.current_user_id = 1;
const expectedData = Object.assign({
search: query,
per_page: 20,
@@ -119,6 +120,27 @@ describe('Api', () => {
done();
});
});
+
+ it('fetches projects without membership when not logged in', (done) => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedData = Object.assign({
+ search: query,
+ per_page: 20,
+ }, options);
+ spyOn(jQuery, 'ajax').and.callFake((request) => {
+ expect(request.url).toEqual(expectedUrl);
+ expect(request.dataType).toEqual('json');
+ expect(request.data).toEqual(expectedData);
+ return sendDummyResponse();
+ });
+
+ Api.projects(query, options, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
});
describe('newLabel', () => {
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index cc336180ff7..3d36bb3e4d4 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -7,6 +7,7 @@ import '~/project_select';
import '~/project';
describe('Project Title', () => {
+ const dummyApiVersion = 'v3000';
preloadFixtures('issues/open-issue.html.raw');
loadJSONFixtures('projects.json');
@@ -14,7 +15,7 @@ describe('Project Title', () => {
loadFixtures('issues/open-issue.html.raw');
window.gon = {};
- window.gon.api_version = 'v3';
+ window.gon.api_version = dummyApiVersion;
// eslint-disable-next-line no-new
new Project();
@@ -37,9 +38,10 @@ describe('Project Title', () => {
it('toggles dropdown', () => {
const $menu = $('.js-dropdown-menu-projects');
+ window.gon.current_user_id = 1;
$('.js-projects-dropdown-toggle').click();
expect($menu).toHaveClass('open');
- expect(reqUrl).toBe('/api/v3/projects.json?simple=true');
+ expect(reqUrl).toBe(`/api/${dummyApiVersion}/projects.json?simple=true`);
expect(reqData).toEqual({
search: '',
order_by: 'last_activity_at',
diff --git a/spec/lib/gitlab/bare_repository_importer_spec.rb b/spec/lib/gitlab/bare_repository_importer_spec.rb
index 892f2dafc96..36d1844b5b1 100644
--- a/spec/lib/gitlab/bare_repository_importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_importer_spec.rb
@@ -2,67 +2,99 @@ require 'spec_helper'
describe Gitlab::BareRepositoryImporter, repository: true do
subject(:importer) { described_class.new('default', project_path) }
- let(:project_path) { 'a-group/a-sub-group/a-project' }
+
let!(:admin) { create(:admin) }
before do
allow(described_class).to receive(:log)
end
- describe '.execute' do
- it 'creates a project for a repository in storage' do
- FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project_path}.git"))
- fake_importer = double
+ shared_examples 'importing a repository' do
+ describe '.execute' do
+ it 'creates a project for a repository in storage' do
+ FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project_path}.git"))
+ fake_importer = double
- expect(described_class).to receive(:new).with('default', project_path)
- .and_return(fake_importer)
- expect(fake_importer).to receive(:create_project_if_needed)
+ expect(described_class).to receive(:new).with('default', project_path)
+ .and_return(fake_importer)
+ expect(fake_importer).to receive(:create_project_if_needed)
- described_class.execute
- end
+ described_class.execute
+ end
- it 'skips wiki repos' do
- FileUtils.mkdir_p(File.join(TestEnv.repos_path, 'the-group', 'the-project.wiki.git'))
+ it 'skips wiki repos' do
+ FileUtils.mkdir_p(File.join(TestEnv.repos_path, 'the-group', 'the-project.wiki.git'))
- expect(described_class).to receive(:log).with(' * Skipping wiki repo')
- expect(described_class).not_to receive(:new)
+ expect(described_class).to receive(:log).with(' * Skipping wiki repo')
+ expect(described_class).not_to receive(:new)
- described_class.execute
+ described_class.execute
+ end
end
- end
- describe '#initialize' do
- context 'without admin users' do
- let(:admin) { nil }
+ describe '#initialize' do
+ context 'without admin users' do
+ let(:admin) { nil }
- it 'raises an error' do
- expect { importer }.to raise_error(Gitlab::BareRepositoryImporter::NoAdminError)
+ it 'raises an error' do
+ expect { importer }.to raise_error(Gitlab::BareRepositoryImporter::NoAdminError)
+ end
end
end
- end
- describe '#create_project_if_needed' do
- it 'starts an import for a project that did not exist' do
- expect(importer).to receive(:create_project)
+ describe '#create_project_if_needed' do
+ it 'starts an import for a project that did not exist' do
+ expect(importer).to receive(:create_project)
+
+ importer.create_project_if_needed
+ end
+
+ it 'skips importing when the project already exists' do
+ project = create(:project, path: 'a-project', namespace: existing_group)
+
+ expect(importer).not_to receive(:create_project)
+ expect(importer).to receive(:log).with(" * #{project.name} (#{project_path}) exists")
+
+ importer.create_project_if_needed
+ end
+
+ it 'creates a project with the correct path in the database' do
+ importer.create_project_if_needed
- importer.create_project_if_needed
+ expect(Project.find_by_full_path(project_path)).not_to be_nil
+ end
end
+ end
+
+ context 'with subgroups', :nested_groups do
+ let(:project_path) { 'a-group/a-sub-group/a-project' }
- it 'skips importing when the project already exists' do
+ let(:existing_group) do
group = create(:group, path: 'a-group')
- subgroup = create(:group, path: 'a-sub-group', parent: group)
- project = create(:project, path: 'a-project', namespace: subgroup)
+ create(:group, path: 'a-sub-group', parent: group)
+ end
- expect(importer).not_to receive(:create_project)
- expect(importer).to receive(:log).with(" * #{project.name} (a-group/a-sub-group/a-project) exists")
+ it_behaves_like 'importing a repository'
+ end
- importer.create_project_if_needed
- end
+ context 'without subgroups' do
+ let(:project_path) { 'a-group/a-project' }
+ let(:existing_group) { create(:group, path: 'a-group') }
- it 'creates a project with the correct path in the database' do
- importer.create_project_if_needed
+ it_behaves_like 'importing a repository'
+ end
+
+ context 'when subgroups are not available' do
+ let(:project_path) { 'a-group/a-sub-group/a-project' }
+
+ before do
+ expect(Group).to receive(:supports_nested_groups?) { false }
+ end
- expect(Project.find_by_full_path(project_path)).not_to be_nil
+ describe '#create_project_if_needed' do
+ it 'raises an error' do
+ expect { importer.create_project_if_needed }.to raise_error('Nested groups are not supported on MySQL')
+ end
end
end
end
diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb
index 4102d57e368..094c9bc604e 100644
--- a/spec/migrations/migrate_stages_statuses_spec.rb
+++ b/spec/migrations/migrate_stages_statuses_spec.rb
@@ -12,7 +12,7 @@ describe MigrateStagesStatuses, :migration do
before do
stub_const("#{described_class.name}::BATCH_SIZE", 2)
- stub_const("#{described_class.name}::RANGE_SIZE", 2)
+ stub_const("#{described_class.name}::RANGE_SIZE", 1)
projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2')
@@ -50,9 +50,10 @@ describe MigrateStagesStatuses, :migration do
Timecop.freeze do
migrate!
- expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 2, 2)
expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, 3, 3)
- expect(BackgroundMigrationWorker.jobs.size).to eq 2
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 313c38cd29c..a7557c7fb22 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -20,10 +20,15 @@ describe API::Groups do
describe "GET /groups" do
context "when unauthenticated" do
- it "returns authentication error" do
+ it "returns public groups" do
get api("/groups")
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response)
+ .to satisfy_one { |group| group['name'] == group1.name }
end
end
@@ -165,6 +170,18 @@ describe API::Groups do
end
describe "GET /groups/:id" do
+ context 'when unauthenticated' do
+ it 'returns 404 for a private group' do
+ get api("/groups/#{group2.id}")
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns 200 for a public group' do
+ get api("/groups/#{group1.id}")
+ expect(response).to have_http_status(200)
+ end
+ end
+
context "when authenticated as user" do
it "returns one of user1's groups" do
project = create(:project, namespace: group2, path: 'Foo')
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 6d11edb5842..6491fb34777 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -2,52 +2,87 @@ require 'spec_helper'
describe Groups::NestedCreateService do
let(:user) { create(:user) }
- let(:params) { { group_path: 'a-group/a-sub-group' } }
subject(:service) { described_class.new(user, params) }
- describe "#execute" do
- it 'returns the group if it already existed' do
- parent = create(:group, path: 'a-group', owner: user)
- child = create(:group, path: 'a-sub-group', parent: parent, owner: user)
+ shared_examples 'with a visibility level' do
+ it 'creates the group with correct visibility level' do
+ allow(Gitlab::CurrentSettings.current_application_settings)
+ .to receive(:default_group_visibility) { Gitlab::VisibilityLevel::INTERNAL }
+
+ group = service.execute
- expect(service.execute).to eq(child)
+ expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'reuses a parent if it already existed', :nested_groups do
- parent = create(:group, path: 'a-group')
- parent.add_owner(user)
+ context 'adding a visibility level ' do
+ it 'overwrites the visibility level' do
+ service = described_class.new(user, params.merge(visibility_level: Gitlab::VisibilityLevel::PRIVATE))
+
+ group = service.execute
- expect(service.execute.parent).to eq(parent)
+ expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
end
+ end
+
+ describe 'without subgroups' do
+ let(:params) { { group_path: 'a-group' } }
- it 'creates group and subgroup in the database', :nested_groups do
- service.execute
+ before do
+ allow(Group).to receive(:supports_nested_groups?) { false }
+ end
- parent = Group.find_by_full_path('a-group')
- child = parent.children.find_by(path: 'a-sub-group')
+ it 'creates the group' do
+ group = service.execute
- expect(parent).not_to be_nil
- expect(child).not_to be_nil
+ expect(group).to be_persisted
end
- it 'creates the group with correct visibility level' do
- allow(Gitlab::CurrentSettings.current_application_settings)
- .to receive(:default_group_visibility) { Gitlab::VisibilityLevel::INTERNAL }
+ it 'returns the group if it already existed' do
+ existing_group = create(:group, path: 'a-group')
- group = service.execute
+ expect(service.execute).to eq(existing_group)
+ end
- expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ it 'raises an error when tring to create a subgroup' do
+ service = described_class.new(user, group_path: 'a-group/a-sub-group')
+
+ expect { service.execute }.to raise_error('Nested groups are not supported on MySQL')
end
- context 'adding a visibility level ' do
- let(:params) { { group_path: 'a-group/a-sub-group', visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
+ it_behaves_like 'with a visibility level'
+ end
- it 'overwrites the visibility level' do
- group = service.execute
+ describe 'with subgroups', :nested_groups do
+ let(:params) { { group_path: 'a-group/a-sub-group' } }
- expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ describe "#execute" do
+ it 'returns the group if it already existed' do
+ parent = create(:group, path: 'a-group', owner: user)
+ child = create(:group, path: 'a-sub-group', parent: parent, owner: user)
+
+ expect(service.execute).to eq(child)
end
+
+ it 'reuses a parent if it already existed' do
+ parent = create(:group, path: 'a-group')
+ parent.add_owner(user)
+
+ expect(service.execute.parent).to eq(parent)
+ end
+
+ it 'creates group and subgroup in the database' do
+ service.execute
+
+ parent = Group.find_by_full_path('a-group')
+ child = parent.children.find_by(path: 'a-sub-group')
+
+ expect(parent).not_to be_nil
+ expect(child).not_to be_nil
+ end
+
+ it_behaves_like 'with a visibility level'
end
end
end