Commit aac0559d authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-06-13

# Conflicts:
#	app/views/admin/application_settings/_account_and_limit.html.haml
#	app/views/admin/application_settings/_ci_cd.html.haml
#	app/views/admin/application_settings/_email.html.haml
#	app/views/admin/application_settings/_help_page.html.haml
#	app/views/admin/application_settings/_repository_mirrors_form.html.haml
#	app/views/admin/application_settings/_signup.html.haml
#	app/views/admin/application_settings/_visibility_and_access.html.haml
#	lib/api/users.rb

[ci skip]
parents 1439b7cb 879b4bba
...@@ -26,13 +26,18 @@ module ChatMessage ...@@ -26,13 +26,18 @@ module ChatMessage
end end
end end
def pretext def summary
return message if markdown return message if markdown
format(message) format(message)
end end
def pretext
summary
end
def fallback def fallback
format(message)
end end
def attachments def attachments
......
...@@ -23,10 +23,6 @@ module ChatMessage ...@@ -23,10 +23,6 @@ module ChatMessage
'' ''
end end
def fallback
format(message)
end
def attachments def attachments
return message if markdown return message if markdown
......
...@@ -44,7 +44,7 @@ class MicrosoftTeamsService < ChatNotificationService ...@@ -44,7 +44,7 @@ class MicrosoftTeamsService < ChatNotificationService
def notify(message, opts) def notify(message, opts)
MicrosoftTeams::Notifier.new(webhook).ping( MicrosoftTeams::Notifier.new(webhook).ping(
title: message.project_name, title: message.project_name,
pretext: message.pretext, summary: message.summary,
activity: message.activity, activity: message.activity,
attachments: message.attachments attachments: message.attachments
) )
......
...@@ -19,4 +19,9 @@ class Timelog < ActiveRecord::Base ...@@ -19,4 +19,9 @@ class Timelog < ActiveRecord::Base
errors.add(:base, 'Issue or Merge Request ID is required') errors.add(:base, 'Issue or Merge Request ID is required')
end end
end end
# Rails5 defaults to :touch_later, overwrite for normal touch
def belongs_to_touch_method
:touch
end
end end
...@@ -65,10 +65,10 @@ class FileUploader < GitlabUploader ...@@ -65,10 +65,10 @@ class FileUploader < GitlabUploader
SecureRandom.hex SecureRandom.hex
end end
def upload_paths(filename) def upload_paths(identifier)
[ [
File.join(secret, filename), File.join(secret, identifier),
File.join(base_dir(Store::REMOTE), secret, filename) File.join(base_dir(Store::REMOTE), secret, identifier)
] ]
end end
......
...@@ -10,6 +10,17 @@ module ObjectStorage ...@@ -10,6 +10,17 @@ module ObjectStorage
UnknownStoreError = Class.new(StandardError) UnknownStoreError = Class.new(StandardError)
ObjectStorageUnavailable = Class.new(StandardError) ObjectStorageUnavailable = Class.new(StandardError)
class ExclusiveLeaseTaken < StandardError
def initialize(lease_key)
@lease_key = lease_key
end
def message
*lease_key_group, _ = *@lease_key.split(":")
"Exclusive lease for #{lease_key_group.join(':')} is already taken."
end
end
TMP_UPLOAD_PATH = 'tmp/uploads'.freeze TMP_UPLOAD_PATH = 'tmp/uploads'.freeze
module Store module Store
...@@ -29,7 +40,7 @@ module ObjectStorage ...@@ -29,7 +40,7 @@ module ObjectStorage
end end
def retrieve_from_store!(identifier) def retrieve_from_store!(identifier)
paths = store_dirs.map { |store, path| File.join(path, identifier) } paths = upload_paths(identifier)
unless current_upload_satisfies?(paths, model) unless current_upload_satisfies?(paths, model)
# the upload we already have isn't right, find the correct one # the upload we already have isn't right, find the correct one
...@@ -261,7 +272,7 @@ module ObjectStorage ...@@ -261,7 +272,7 @@ module ObjectStorage
end end
def delete_migrated_file(migrated_file) def delete_migrated_file(migrated_file)
migrated_file.delete if exists? migrated_file.delete
end end
def exists? def exists?
...@@ -279,6 +290,13 @@ module ObjectStorage ...@@ -279,6 +290,13 @@ module ObjectStorage
} }
end end
# Returns all the possible paths for an upload.
# the `upload.path` is a lookup parameter, and it may change
# depending on the `store` param.
def upload_paths(identifier)
store_dirs.map { |store, path| File.join(path, identifier) }
end
def cache!(new_file = sanitized_file) def cache!(new_file = sanitized_file)
# We intercept ::UploadedFile which might be stored on remote storage # We intercept ::UploadedFile which might be stored on remote storage
# We use that for "accelerated" uploads, where we store result on remote storage # We use that for "accelerated" uploads, where we store result on remote storage
...@@ -369,12 +387,13 @@ module ObjectStorage ...@@ -369,12 +387,13 @@ module ObjectStorage
end end
def with_exclusive_lease def with_exclusive_lease
uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain lease_key = exclusive_lease_key
raise 'exclusive lease already taken' unless uuid uuid = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.hour.to_i).try_obtain
raise ExclusiveLeaseTaken.new(lease_key) unless uuid
yield uuid yield uuid
ensure ensure
Gitlab::ExclusiveLease.cancel(exclusive_lease_key, uuid) Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end end
# #
......
...@@ -22,7 +22,7 @@ module RecordsUploads ...@@ -22,7 +22,7 @@ module RecordsUploads
Upload.transaction do Upload.transaction do
uploads.where(path: upload_path).delete_all uploads.where(path: upload_path).delete_all
upload.destroy! if upload upload.delete if upload
self.upload = build_upload.tap(&:save!) self.upload = build_upload.tap(&:save!)
end end
......
...@@ -13,9 +13,12 @@ ...@@ -13,9 +13,12 @@
.form-group .form-group
= f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'label-light' = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'label-light'
= f.number_field :max_attachment_size, class: 'form-control' = f.number_field :max_attachment_size, class: 'form-control'
<<<<<<< HEAD
= render 'repository_size_limit_setting', form: f = render 'repository_size_limit_setting', form: f
=======
>>>>>>> upstream/master
.form-group .form-group
= f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-light' = f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-light'
= f.number_field :session_expire_delay, class: 'form-control' = f.number_field :session_expire_delay, class: 'form-control'
...@@ -32,6 +35,7 @@ ...@@ -32,6 +35,7 @@
= f.check_box :user_default_external, class: 'form-check-input' = f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do = f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external Newly registered users will by default be external
<<<<<<< HEAD
- if ::Gitlab.dev_env_or_com? - if ::Gitlab.dev_env_or_com?
.form-group .form-group
...@@ -41,5 +45,7 @@ ...@@ -41,5 +45,7 @@
= f.label :check_namespace_plan, class: 'form-check-label' do = f.label :check_namespace_plan, class: 'form-check-label' do
Enabling this will only make licensed EE features available to projects if the project namespace's plan Enabling this will only make licensed EE features available to projects if the project namespace's plan
includes the feature or if the project is public. includes the feature or if the project is public.
=======
>>>>>>> upstream/master
= f.submit 'Save changes', class: 'btn btn-success' = f.submit 'Save changes', class: 'btn btn-success'
...@@ -20,9 +20,12 @@ ...@@ -20,9 +20,12 @@
= f.check_box :shared_runners_enabled, class: 'form-check-input' = f.check_box :shared_runners_enabled, class: 'form-check-input'
= f.label :shared_runners_enabled, class: 'form-check-label' do = f.label :shared_runners_enabled, class: 'form-check-label' do
Enable shared runners for new projects Enable shared runners for new projects
<<<<<<< HEAD
= render 'shared_runners_minutes_setting', form: f = render 'shared_runners_minutes_setting', form: f
=======
>>>>>>> upstream/master
.form-group .form-group
= f.label :shared_runners_text, class: 'label-light' = f.label :shared_runners_text, class: 'label-light'
= f.text_area :shared_runners_text, class: 'form-control', rows: 4 = f.text_area :shared_runners_text, class: 'form-control', rows: 4
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
By default GitLab sends emails in HTML and plain text formats so mail By default GitLab sends emails in HTML and plain text formats so mail
clients can choose what format to use. Disable this option if you only clients can choose what format to use. Disable this option if you only
want to send emails in plain text format. want to send emails in plain text format.
<<<<<<< HEAD
-# EE-specific start -# EE-specific start
- if License.feature_available?(:email_additional_text) - if License.feature_available?(:email_additional_text)
.form-group .form-group
...@@ -28,5 +29,7 @@ ...@@ -28,5 +29,7 @@
.form-text.text-muted .form-text.text-muted
= _('Add additional text to appear in all email communications. %{character_limit} character limit') % { character_limit: number_with_delimiter(Gitlab::CurrentSettings.email_additional_text_character_limit) } = _('Add additional text to appear in all email communications. %{character_limit} character limit') % { character_limit: number_with_delimiter(Gitlab::CurrentSettings.email_additional_text_character_limit) }
-# EE-specific end -# EE-specific end
=======
>>>>>>> upstream/master
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
%fieldset %fieldset
.form-group .form-group
<<<<<<< HEAD
= f.label :help_text, class: 'label-light' = f.label :help_text, class: 'label-light'
= f.text_area :help_text, class: 'form-control', rows: 4 = f.text_area :help_text, class: 'form-control', rows: 4
.form-text.text-muted Markdown enabled .form-text.text-muted Markdown enabled
.form-group .form-group
=======
>>>>>>> upstream/master
= f.label :help_page_text, class: 'label-light' = f.label :help_page_text, class: 'label-light'
= f.text_area :help_page_text, class: 'form-control', rows: 4 = f.text_area :help_page_text, class: 'form-control', rows: 4
.form-text.text-muted Markdown enabled .form-text.text-muted Markdown enabled
......
...@@ -11,8 +11,11 @@ ...@@ -11,8 +11,11 @@
%span.form-text.text-muted %span.form-text.text-muted
If disabled, only admins will be able to setup mirrors in projects. If disabled, only admins will be able to setup mirrors in projects.
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring') = link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
<<<<<<< HEAD
- if Gitlab.com? && License.feature_available?(:repository_mirrors) - if Gitlab.com? && License.feature_available?(:repository_mirrors)
= render 'mirror_settings', f: f = render 'mirror_settings', f: f
=======
>>>>>>> upstream/master
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
...@@ -24,7 +24,11 @@ ...@@ -24,7 +24,11 @@
Enable domain blacklist for sign ups Enable domain blacklist for sign ups
.form-group .form-group
.form-check .form-check
<<<<<<< HEAD
= radio_button_tag :blacklist_type, :file, false, class: "form-check-input" = radio_button_tag :blacklist_type, :file, false, class: "form-check-input"
=======
= radio_button_tag :blacklist_type, :file, false, class: 'form-check-input'
>>>>>>> upstream/master
= label_tag :blacklist_type_file, class: 'form-check-label' do = label_tag :blacklist_type_file, class: 'form-check-label' do
.option-title .option-title
Upload blacklist file Upload blacklist file
......
...@@ -5,7 +5,10 @@ ...@@ -5,7 +5,10 @@
.form-group .form-group
= f.label :default_branch_protection, class: 'label-light' = f.label :default_branch_protection, class: 'label-light'
= f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
<<<<<<< HEAD
= render partial: 'admin/application_settings/ee/project_creation_level', locals: { form: f, application_setting: @application_setting } = render partial: 'admin/application_settings/ee/project_creation_level', locals: { form: f, application_setting: @application_setting }
=======
>>>>>>> upstream/master
.form-group.visibility-level-setting .form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'label-light' = f.label :default_project_visibility, class: 'label-light'
= render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new) = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
...@@ -20,6 +23,7 @@ ...@@ -20,6 +23,7 @@
- checkbox_name = 'application_setting[restricted_visibility_levels][]' - checkbox_name = 'application_setting[restricted_visibility_levels][]'
= hidden_field_tag(checkbox_name) = hidden_field_tag(checkbox_name)
- restricted_level_checkboxes('restricted-visibility-help', checkbox_name, class: 'form-check-input').each do |level| - restricted_level_checkboxes('restricted-visibility-help', checkbox_name, class: 'form-check-input').each do |level|
<<<<<<< HEAD
.form-check .form-check
= level = level
%span.form-text.text-muted#restricted-visibility-help %span.form-text.text-muted#restricted-visibility-help
...@@ -49,6 +53,25 @@ ...@@ -49,6 +53,25 @@
%span.form-text.text-muted %span.form-text.text-muted
If checked, group owners can manage LDAP group links and LDAP member overrides If checked, group owners can manage LDAP group links and LDAP member overrides
= link_to icon('question-circle'), help_page_path('administration/auth/ldap-ee') = link_to icon('question-circle'), help_page_path('administration/auth/ldap-ee')
=======
.form-check
= level
%span.form-text.text-muted#restricted-visibility-help
Selected levels cannot be used by non-admin users for groups, projects or snippets.
If the public level is restricted, user profiles are only visible to logged in users.
.form-group
= f.label :import_sources, class: 'label-light'
= hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help', class: 'form-check-input').each do |source|
.form-check= source
%span.form-text.text-muted#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
= link_to "(?)", help_page_path("integration/github")
, Bitbucket
= link_to "(?)", help_page_path("integration/bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration/gitlab")
>>>>>>> upstream/master
.form-group .form-group
.form-check .form-check
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%h4 %h4
= s_('WikiEmpty|The wiki lets you write documentation for your project') = s_('WikiEmpty|The wiki lets you write documentation for your project')
%p.text-left %p.text-left
= s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, it's principles, how to use it, and so on.") = s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
= create_link = create_link
- elsif can?(current_user, :read_issue, @project) - elsif can?(current_user, :read_issue, @project)
......
---
title: Fixes Microsoft Teams notifications for pipeline events
merge_request: 19632
author: Jeff Brown
type: fixed
---
title: Use one column form layout on Admin Area Settings page
merge_request:
author:
type: changed
---
title: Optimize the upload migration proces
merge_request: 15947
author:
type: fixed
---
title: Added with_statsoption for GET /projects/:id/repository/commits
merge_request:
author:
type: added
---
title: Restore API v3 user endpoint
merge_request:
author:
type: changed
---
title: Rails5 fix expected `issuable.reload.updated_at` to have changed
merge_request: 19733
author: Jasper Maes
type: fixed
...@@ -14,6 +14,7 @@ module Gitlab ...@@ -14,6 +14,7 @@ module Gitlab
require_dependency Rails.root.join('lib/gitlab/redis/shared_state') require_dependency Rails.root.join('lib/gitlab/redis/shared_state')
require_dependency Rails.root.join('lib/gitlab/request_context') require_dependency Rails.root.join('lib/gitlab/request_context')
require_dependency Rails.root.join('lib/gitlab/current_settings') require_dependency Rails.root.join('lib/gitlab/current_settings')
require_dependency Rails.root.join('lib/gitlab/middleware/read_only')
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
...@@ -203,7 +204,7 @@ module Gitlab ...@@ -203,7 +204,7 @@ module Gitlab
ENV['GIT_TERMINAL_PROMPT'] = '0' ENV['GIT_TERMINAL_PROMPT'] = '0'
# Gitlab Read-only middleware support # Gitlab Read-only middleware support
config.middleware.insert_after ActionDispatch::Flash, '::Gitlab::Middleware::ReadOnly' config.middleware.insert_after ActionDispatch::Flash, ::Gitlab::Middleware::ReadOnly
config.generators do |g| config.generators do |g|
g.factory_bot false g.factory_bot false
......
Rails.application.configure do Rails.application.configure do
# Make sure the middleware is inserted first in middleware chain # Make sure the middleware is inserted first in middleware chain
config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestBlockerMiddleware') config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestBlockerMiddleware)
config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestInspectorMiddleware') config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestInspectorMiddleware)
# Settings specified here will take precedence over those in config/application.rb # Settings specified here will take precedence over those in config/application.rb
......
...@@ -16,6 +16,7 @@ GET /projects/:id/repository/commits ...@@ -16,6 +16,7 @@ GET /projects/:id/repository/commits
| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `path` | string | no | The file path | | `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository | | `all` | boolean | no | Retrieve every commit from the repository |
| `with_stats` | boolean | no | Stats about each commit will be added to the response |
```bash ```bash
......
...@@ -15,19 +15,21 @@ module API ...@@ -15,19 +15,21 @@ module API
end end
params do params do
optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned' optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned' optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
optional :path, type: String, desc: 'The file path' optional :path, type: String, desc: 'The file path'
optional :all, type: Boolean, desc: 'Every commit will be returned' optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
use :pagination use :pagination
end end
get ':id/repository/commits' do get ':id/repository/commits' do
path = params[:path] path = params[:path]
before = params[:until] before = params[:until]
after = params[:since] after = params[:since]
ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all] ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page] offset = (params[:page] - 1) * params[:per_page]
all = params[:all] all = params[:all]
with_stats = params[:with_stats]
commits = user_project.repository.commits(ref, commits = user_project.repository.commits(ref,
path: path, path: path,
...@@ -47,7 +49,9 @@ module API ...@@ -47,7 +49,9 @@ module API
paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count)
present paginate(paginated_commits), with: Entities::Commit serializer = with_stats ? Entities::CommitWithStats : Entities::Commit
present paginate(paginated_commits), with: serializer
end end
desc 'Commit multiple file changes as one commit' do desc 'Commit multiple file changes as one commit' do
......
...@@ -308,6 +308,10 @@ module API ...@@ -308,6 +308,10 @@ module API
expose :additions, :deletions, :total expose :additions, :deletions, :total
end end
class CommitWithStats < Commit
expose :stats, using: Entities::CommitStats
end
class CommitDetail < Commit class CommitDetail < Commit
expose :stats, using: Entities::CommitStats, if: :stats expose :stats, using: Entities::CommitStats, if: :stats
expose :status expose :status
......
...@@ -537,7 +537,11 @@ module API ...@@ -537,7 +537,11 @@ module API
authenticate! authenticate!
end end
<<<<<<< HEAD
# Enabling /users/:id endpoint for the v3 version to allow oauth # Enabling /users/:id endpoint for the v3 version to allow oauth
=======
# Enabling /user endpoint for the v3 version to allow oauth
>>>>>>> upstream/master
# authentication through this endpoint. # authentication through this endpoint.
version %w(v3 v4), using: :path do version %w(v3 v4), using: :path do
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
......
...@@ -1185,18 +1185,18 @@ module Gitlab ...@@ -1185,18 +1185,18 @@ module Gitlab
end end
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:) def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
Gitlab::GitalyClient::StorageSettings.allow_disk_access do tmp_ref = "refs/tmp/#{SecureRandom.hex}"
with_repo_branch_commit(source_repository, source_branch_name) do |commit|
break unless commit
Gitlab::Git::Compare.new( return unless fetch_source_branch!(source_repository, source_branch_name, tmp_ref)
self,
target_branch_name, Gitlab::Git::Compare.new(
commit.sha, self,
straight: straight target_branch_name,
) tmp_ref,
end straight: straight
end )
ensure
delete_refs(tmp_ref)
end end
def write_ref(ref_path, ref, old_ref: nil, shell: true) def write_ref(ref_path, ref, old_ref: nil, shell: true)
......
...@@ -30,7 +30,7 @@ module MicrosoftTeams ...@@ -30,7 +30,7 @@ module MicrosoftTeams
result = { 'sections' => [] } result = { 'sections' => [] }
result['title'] = options[:title] result['title'] = options[:title]
result['summary'] = options[:pretext] result['summary'] = options[:summary]
result['sections'] << MicrosoftTeams::Activity.new(options[:activity]).prepare result['sections'] << MicrosoftTeams::Activity.new(options[:activity]).prepare
attachments = options[:attachments] attachments = options[:attachments]
......
...@@ -5859,7 +5859,7 @@ msgstr "" ...@@ -5859,7 +5859,7 @@ msgstr ""
msgid "WikiEmptyIssueMessage|issue tracker" msgid "WikiEmptyIssueMessage|issue tracker"
msgstr "" msgstr ""
msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, it's principles, how to use it, and so on." msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on."
msgstr "" msgstr ""
msgid "WikiEmpty|Create your first page" msgid "WikiEmpty|Create your first page"
......
{
"type": "object",
"allOf": [
{ "$ref": "basic.json" },
{
"required" : [
"stats"
],
"properties": {
"stats": { "$ref": "../commit_stats.json" }
}
}
]
}
{
"type": "array",
"items": { "$ref": "commit/with_stats.json" }
}
...@@ -8,7 +8,7 @@ describe MicrosoftTeams::Notifier do ...@@ -8,7 +8,7 @@ describe MicrosoftTeams::Notifier do
let(:options) do let(:options) do
{ {
title: 'JohnDoe4/project2', title: 'JohnDoe4/project2',
pretext: '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6', summary: '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6',
activity: { activity: {
title: 'Issue opened by user6', title: 'Issue opened by user6',
subtitle: 'in [JohnDoe4/project2](http://localhost/namespace2/gitlabhq)', subtitle: 'in [JohnDoe4/project2](http://localhost/namespace2/gitlabhq)',
......
...@@ -225,10 +225,15 @@ describe MicrosoftTeamsService do ...@@ -225,10 +225,15 @@ describe MicrosoftTeamsService do
it 'calls Microsoft Teams API for pipeline events' do it 'calls Microsoft Teams API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline) data = Gitlab::DataBuilder::Pipeline.build(pipeline)
data[:markdown] = true
chat_service.execute(data) chat_service.execute(data)
expect(WebMock).to have_requested(:post, webhook_url).once message = ChatMessage::PipelineMessage.new(data)
expect(WebMock).to have_requested(:post, webhook_url)
.with(body: hash_including({ summary: message.summary }))
.once
end end
end end
......
...@@ -18,14 +18,14 @@ describe API::Commits do ...@@ -18,14 +18,14 @@ describe API::Commits do
describe 'GET /projects/:id/repository/commits' do describe 'GET /projects/:id/repository/commits' do
let(:route) { "/projects/#{project_id}/repository/commits" } let(:route) { "/projects/#{project_id}/repository/commits" }
shared_examples_for 'project commits' do shared_examples_for 'project commits' do |schema: 'public_api/v4/commits'|
it "returns project commits" do it "returns project commits" do
commit = project.repository.commit commit = project.repository.commit
get api(route, current_user) get api(route, current_user)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/commits') expect(response).to match_response_schema(schema)
expect(json_response.first['id']).to eq(commit.id) expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['committer_name']).to eq(commit.committer_name) expect(json_response.first['committer_name']).to eq(commit.committer_name)
expect(json_response.first['committer_email']).to eq(commit.committer_email) expect(json_response.first['committer_email']).to eq(commit.committer_email)
...@@ -161,6 +161,23 @@ describe API::Commits do ...@@ -161,6 +161,23 @@ describe API::Commits do
end end
end end
context 'with_stats optional parameter' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'project commits', schema: 'public_api/v4/commits_with_stats' do
let(:route) { "/projects/#{project_id}/repository/commits?with_stats=true" }
it 'include commits details' do
commit = project.repository.commit
get api(route, current_user)
expect(json_response.first['stats']['additions']).to eq(commit.stats.additions)
expect(json_response.first['stats']['deletions']).to eq(commit.stats.deletions)
expect(json_response.first['stats']['total']).to eq(commit.stats.total)
end
end
end
context 'with pagination params' do context 'with pagination params' do
let(:page) { 1 } let(:page) { 1 }
let(:per_page) { 5 } let(:per_page) { 5 }
......
...@@ -85,13 +85,13 @@ shared_examples "migrates" do |to_store:, from_store: nil| ...@@ -85,13 +85,13 @@ shared_examples "migrates" do |to_store:, from_store: nil|
it 'does not execute migrate!' do it 'does not execute migrate!' do
expect(subject).not_to receive(:unsafe_migrate!) expect(subject).not_to receive(:unsafe_migrate!)
expect { migrate(to) }.to raise_error('exclusive lease already taken') expect { migrate(to) }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end end
it 'does not execute use_file' do it 'does not execute use_file' do
expect(subject).not_to receive(:unsafe_use_file) expect(subject).not_to receive(:unsafe_use_file)
expect { subject.use_file }.to raise_error('exclusive lease already taken') expect { subject.use_file }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end end
after do after do
......
...@@ -333,7 +333,7 @@ describe ObjectStorage do ...@@ -333,7 +333,7 @@ describe ObjectStorage do
when_file_is_in_use do when_file_is_in_use do
expect(uploader).not_to receive(:unsafe_migrate!) expect(uploader).not_to receive(:unsafe_migrate!)
expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error('exclusive lease already taken') expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end end
end end
...@@ -341,7 +341,7 @@ describe ObjectStorage do ...@@ -341,7 +341,7 @@ describe ObjectStorage do
when_file_is_in_use do when_file_is_in_use do
expect(uploader).not_to receive(:unsafe_use_file) expect(uploader).not_to receive(:unsafe_use_file)
expect { uploader.use_file }.to raise_error('exclusive lease already taken') expect { uploader.use_file }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end end
end end
end end
......
...@@ -11,6 +11,12 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -11,6 +11,12 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
let(:uploads) { Upload.all } let(:uploads) { Upload.all }
let(:to_store) { ObjectStorage::Store::REMOTE } let(:to_store) { ObjectStorage::Store::REMOTE }
def perform(uploads)
described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
# swallow
end
shared_examples "uploads migration worker" do shared_examples "uploads migration worker" do
describe '.enqueue!' do describe '.enqueue!' do
def enqueue! def enqueue!
...@@ -69,12 +75,6 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -69,12 +75,6 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
end end
describe '#perform' do describe '#perform' do
def perform
described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
# swallow
end
shared_examples 'outputs correctly' do |success: 0, failures: 0| shared_examples 'outputs correctly' do |success: 0, failures: 0|
total = success + failures total = success + failures
...@@ -82,7 +82,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -82,7 +82,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it 'outputs the reports' do it 'outputs the reports' do
expect(Rails.logger).to receive(:info).with(%r{Migrated #{success}/#{total} files}) expect(Rails.logger).to receive(:info).with(%r{Migrated #{success}/#{total} files})
perform perform(uploads)
end end
end end
...@@ -90,7 +90,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -90,7 +90,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it 'outputs upload failures' do it 'outputs upload failures' do
expect(Rails.logger).to receive(:warn).with(/Error .* I am a teapot/) expect(Rails.logger).to receive(:warn).with(/Error .* I am a teapot/)
perform perform(uploads)
end end
end end
end end
...@@ -98,7 +98,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -98,7 +98,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it_behaves_like 'outputs correctly', success: 10 it_behaves_like 'outputs correctly', success: 10
it 'migrates files' do it 'migrates files' do
perform perform(uploads)
expect(Upload.where(store: ObjectStorage::Store::LOCAL).count).to eq(0) expect(Upload.where(store: ObjectStorage::Store::LOCAL).count).to eq(0)
end end
...@@ -123,6 +123,17 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -123,6 +123,17 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
end end
it_behaves_like "uploads migration worker" it_behaves_like "uploads migration worker"
describe "limits N+1 queries" do
it "to N*5" do
query_count = ActiveRecord::QueryRecorder.new { perform(uploads) }
more_projects = create_list(:project, 3, :with_avatar)
expected_queries_per_migration = 5 * more_projects.count
expect { perform(Upload.all) }.not_to exceed_query_limit(query_count).with_threshold(expected_queries_per_migration)
end
end
end end
context "for FileUploader" do context "for FileUploader" do
...@@ -130,15 +141,29 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do ...@@ -130,15 +141,29 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
let(:secret) { SecureRandom.hex } let(:secret) { SecureRandom.hex }
let(:mounted_as) { nil } let(:mounted_as) { nil }
def upload_file(project)
uploader = FileUploader.new(project)
uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
end
before do before do
stub_uploads_object_storage(FileUploader) stub_uploads_object_storage(FileUploader)
projects.map do |project| projects.map(&method(:upload_file))
uploader = FileUploader.new(project)
uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
end
end end
it_behaves_like "uploads migration worker" it_behaves_like "uploads migration worker"
describe "limits N+1 queries" do
it "to N*5" do
query_count = ActiveRecord::QueryRecorder.new { perform(uploads) }
more_projects = create_list(:project, 3)
more_projects.map(&method(:upload_file))
expected_queries_per_migration = 5 * more_projects.count
expect { perform(Upload.all) }.not_to exceed_query_limit(query_count).with_threshold(expected_queries_per_migration)
end
end
end end
end end
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