Commit d89277c3 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 30634-protected-pipeline

* upstream/master: (67 commits)
  Revert "Merge branch 'revert-12499' into 'master'"
  Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion
  Document that GitLab 9.3 requires the TRIGGER permission on MySQL
  Instrument Unicorn with Ruby exporter
  Remove group modal like remove project modal. Closes #33130
  Update prometheus client gem
  Enables the option in user preferences to turn on the new navigation
  Simplify authentication logic in the v4 users API for !12445.
  wait_for_requests is not needed when AJAX is not in play
  Don't resolve fork relationships for projects pending delete
  Clean up the ForkedProjectLink specs
  Remove unnecessary clear_stubs calls
  Add test for GitalyClient::Ref#find_ref_name
  DeleteMergedBranchesService should not delete protected branches
  Optimize creation of commit API by using Repository#commit instead of Repository#commits
  Update CHANGELOG.md for 9.3.4
  Make autosize fields more performant and remove broken autosize handle
  Update GITLAB_SHELL_VERSION to 5.1.1
  Fixed the y_label not setting correctly for each graph on the monitoring dashboard
  Refactor and copyedit "Using Docker images" docs
  ...
parents 2afa90b6 5af1fcd6
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30) ## 9.3.3 (2017-06-30)
- Fix head pipeline stored in merge request for external pipelines. !12478 - Fix head pipeline stored in merge request for external pipelines. !12478
......
...@@ -285,6 +285,7 @@ group :metrics do ...@@ -285,6 +285,7 @@ group :metrics do
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5' gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end end
group :development do group :development do
......
...@@ -599,8 +599,8 @@ GEM ...@@ -599,8 +599,8 @@ GEM
premailer-rails (1.9.7) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5) prometheus-client-mmap (0.7.0.beta8)
mmap2 (~> 2.2.6) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -658,7 +658,7 @@ GEM ...@@ -658,7 +658,7 @@ GEM
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.17.0) raindrops (0.18.0)
rake (10.5.0) rake (10.5.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
...@@ -1062,6 +1062,7 @@ DEPENDENCIES ...@@ -1062,6 +1062,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9) rails-i18n (~> 4.0.9)
rainbow (~> 2.2) rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rdoc (~> 4.2) rdoc (~> 4.2)
recaptcha (~> 3.0) recaptcha (~> 3.0)
......
import autosize from 'vendor/autosize'; import autosize from 'vendor/autosize';
$(() => { document.addEventListener('DOMContentLoaded', () => {
const $fields = $('.js-autosize'); const autosizeEls = document.querySelectorAll('.js-autosize');
$fields.on('autosize:resized', function resized() { autosize(autosizeEls);
const $field = $(this); autosize.update(autosizeEls);
$field.data('height', $field.outerHeight());
});
$fields.on('resize.autosize', function resize() {
const $field = $(this);
if ($field.data('height') !== $field.outerHeight()) {
$field.data('height', $field.outerHeight());
autosize.destroy($field);
$field.css('max-height', window.outerHeight);
}
});
autosize($fields);
autosize.update($fields);
$fields.css('resize', 'vertical');
}); });
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
} }
this.data = query.result[0].values; this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A'; this.unitOfDisplay = query.unit || 'N/A';
this.yAxisLabel = this.columnData.y_axis || 'Values'; this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average'; this.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right; this.margin.left - this.margin.right;
......
...@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) { ...@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) { return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance // GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance // GitLabDropdownRemote returns this.options.instance
......
...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue def issue
return @issue if defined?(@issue) return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by # The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! @noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder ...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
def projects def projects
return @projects if defined?(@projects) return @projects if defined?(@projects)
@projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute @projects = if skip_authorization
Project.all
else
ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
end
@projects = @projects.in_namespace(params[:group_id]) if group? @projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil) @projects = @projects.reorder(nil)
......
...@@ -60,13 +60,13 @@ class UsersFinder ...@@ -60,13 +60,13 @@ class UsersFinder
end end
def by_external_identity(users) def by_external_identity(users)
return users unless current_user.admin? && params[:extern_uid] && params[:provider] return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end end
def by_external(users) def by_external(users)
return users = users.where.not(external: true) unless current_user.admin? return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external] return users unless params[:external]
users.external users.external
......
...@@ -298,10 +298,6 @@ module ApplicationHelper ...@@ -298,10 +298,6 @@ module ApplicationHelper
end end
end end
def can_toggle_new_nav?
Rails.env.development?
end
def show_new_nav? def show_new_nav?
cookies["new_nav"] == "true" cookies["new_nav"] == "true"
end end
......
...@@ -16,8 +16,8 @@ module FormHelper ...@@ -16,8 +16,8 @@ module FormHelper
end end
end end
def issue_dropdown_options(issuable, has_multiple_assignees = true) def issue_assignees_dropdown_options
options = { {
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data', toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee', title: 'Select assignee',
filter: true, filter: true,
...@@ -27,8 +27,8 @@ module FormHelper ...@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username, first_user: current_user&.username,
null_user: true, null_user: true,
current_user: true, current_user: true,
project_id: issuable.project.try(:id), project_id: @project.id,
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]", field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned', default_label: 'Unassigned',
'max-select': 1, 'max-select': 1,
'dropdown-header': 'Assignee', 'dropdown-header': 'Assignee',
...@@ -38,13 +38,5 @@ module FormHelper ...@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name]) current_user_info: current_user.to_json(only: [:id, :name])
} }
} }
if has_multiple_assignees
options[:title] = 'Select assignee(s)'
options[:data][:'dropdown-header'] = 'Assignee(s)'
options[:data].delete(:'max-select')
end
options
end end
end end
...@@ -58,6 +58,11 @@ module GroupsHelper ...@@ -58,6 +58,11 @@ module GroupsHelper
IssuesFinder.new(current_user, group_id: group.id).execute IssuesFinder.new(current_user, group_id: group.id).execute
end end
def remove_group_message(group)
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
{ group_name: group.name }
end
private private
def group_title_link(group, hidable: false) def group_title_link(group, hidable: false)
......
...@@ -126,6 +126,18 @@ module SearchHelper ...@@ -126,6 +126,18 @@ module SearchHelper
search_path(options) search_path(options)
end end
def search_filter_input_options(type)
{
id: "filtered-search-#{type}",
placeholder: 'Search or filter results...',
data: {
'project-id' => @project.id,
'username-params' => @users.to_json(only: [:id, :username]),
'base-endpoint' => namespace_project_path(@project.namespace, @project)
}
}
end
# Sanitize a HTML field for search display. Most tags are stripped out and the # Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters. # maximum length is set to 200 characters.
def search_md_sanitize(object, field) def search_md_sanitize(object, field)
......
...@@ -176,9 +176,12 @@ module Ci ...@@ -176,9 +176,12 @@ module Ci
# * Lowercased # * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a - # * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes # * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug def ref_slug
slugified = ref.to_s.downcase ref.to_s
slugified.gsub(/[^a-z0-9]/, '-')[0..62] .downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end end
# Variables whose value does not depend on environment # Variables whose value does not depend on environment
......
...@@ -102,6 +102,14 @@ module Issuable ...@@ -102,6 +102,14 @@ module Issuable
def locking_enabled? def locking_enabled?
title_changed? || description_changed? title_changed? || description_changed?
end end
def allows_multiple_assignees?
false
end
def has_multiple_assignees?
assignees.count > 1
end
end end
module ClassMethods module ClassMethods
......
...@@ -5,6 +5,25 @@ ...@@ -5,6 +5,25 @@
module Sortable module Sortable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module DropDefaultScopeOnFinders
# Override these methods to drop the `ORDER BY id DESC` default scope.
# See http://dba.stackexchange.com/a/110919 for why we do this.
%i[find find_by find_by!].each do |meth|
define_method meth do |*args, &block|
return super(*args, &block) if block
unordered_relation = unscope(:order)
# We cannot simply call `meth` on `unscope(:order)`, since that is also
# an instance of the same relation class this module is included into,
# which means we'd get infinite recursion.
# We explicitly use the original implementation to prevent this.
original_impl = method(__method__).super_method.unbind
original_impl.bind(unordered_relation).call(*args)
end
end
end
included do included do
# By default all models should be ordered # By default all models should be ordered
# by created_at field starting from newest # by created_at field starting from newest
...@@ -18,6 +37,10 @@ module Sortable ...@@ -18,6 +37,10 @@ module Sortable
scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) } scope :order_name_desc, -> { reorder(name: :desc) }
# All queries (relations) on this model are instances of this `relation_klass`.
relation_klass = relation_delegate_class(ActiveRecord::Relation)
relation_klass.prepend DropDefaultScopeOnFinders
end end
module ClassMethods module ClassMethods
......
class ForkedProjectLink < ActiveRecord::Base class ForkedProjectLink < ActiveRecord::Base
belongs_to :forked_to_project, class_name: 'Project' belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
belongs_to :forked_from_project, class_name: 'Project' belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
end end
...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
} }
end end
# This method is needed for compatibility with issues to not mess view and other code # These method are needed for compatibility with issues to not mess view and other code
def assignees def assignees
Array(assignee) Array(assignee)
end end
def assignee_ids
Array(assignee_id)
end
def assignee_ids=(ids)
write_attribute(:assignee_id, ids.last)
end
def assignee_or_author?(user) def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id author_id == user.id || assignee_id == user.id
end end
......
...@@ -815,7 +815,7 @@ class Project < ActiveRecord::Base ...@@ -815,7 +815,7 @@ class Project < ActiveRecord::Base
end end
def ci_service def ci_service
@ci_service ||= ci_services.reorder(nil).find_by(active: true) @ci_service ||= ci_services.find_by(active: true)
end end
def deployment_services def deployment_services
...@@ -823,7 +823,7 @@ class Project < ActiveRecord::Base ...@@ -823,7 +823,7 @@ class Project < ActiveRecord::Base
end end
def deployment_service def deployment_service
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true) @deployment_service ||= deployment_services.find_by(active: true)
end end
def monitoring_services def monitoring_services
...@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base ...@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base
end end
def monitoring_service def monitoring_service
@monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) @monitoring_service ||= monitoring_services.find_by(active: true)
end end
def jira_tracker? def jira_tracker?
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin" desc "User is an instance admin"
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:admin) { @user&.admin? } condition(:admin) { @user&.admin? }
...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group } condition(:can_create_group) { @user&.can_create_group }
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
end end
...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy ...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? } condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
end
rule { default }.policy do rule { default }.policy do
enable :read_users_list
enable :log_in enable :log_in
enable :access_api enable :access_api
enable :access_git enable :access_git
...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy ...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do rule { access_locked }.policy do
prevent :log_in prevent :log_in
end end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end end
class UserPolicy < BasePolicy class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
desc "The current user is the user in question" desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user } condition(:user_is_self, score: 0) { @subject == @user }
......
...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService ...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests # Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names branches -= merge_request_branch_names
# Prevent deletion of protected branches
branches -= project.protected_branches.pluck(:name)
branches.each do |branch| branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch) DeleteBranchService.new(project, current_user).execute(branch)
......
...@@ -61,10 +61,14 @@ module MergeRequests ...@@ -61,10 +61,14 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch? if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
# Verify again that the source branch can be removed, since branch may be protected,
# or the source branch may have been updated.
if @merge_request.can_remove_source_branch?(branch_deletion_user)
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user) DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
.execute(merge_request.source_branch) .execute(merge_request.source_branch)
end end
end end
end
def branch_deletion_user def branch_deletion_user
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user @merge_request.force_remove_source_branch? ? @merge_request.author : current_user
......
...@@ -92,9 +92,12 @@ module QuickActions ...@@ -92,9 +92,12 @@ module QuickActions
desc 'Assign' desc 'Assign'
explanation do |users| explanation do |users|
"Assigns #{users.first.to_reference}." if users.any? users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end end
params '@user'
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
...@@ -104,27 +107,68 @@ module QuickActions ...@@ -104,27 +107,68 @@ module QuickActions
command :assign do |users| command :assign do |users|
next if users.empty? next if users.empty?
if issuable.is_a?(Issue) @updates[:assignee_ids] =
@updates[:assignee_ids] = [users.last.id] if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else else
@updates[:assignee_id] = users.last.id [users.last.id]
end end
end end
desc 'Remove assignee' desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else
'Remove assignee'
end
end
explanation do explanation do
"Removes assignee #{issuable.assignees.first.to_reference}." "Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end end
condition do condition do
issuable.persisted? && issuable.persisted? &&
issuable.assignees.any? && issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :unassign do parse_params do |unassign_param|
if issuable.is_a?(Issue) # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
@updates[:assignee_ids] = [] extract_users(unassign_param) if issuable.allows_multiple_assignees?
end
command :unassign do |users = nil|
@updates[:assignee_ids] =
if users&.any?
issuable.assignees.pluck(:id) - users.map(&:id)
else
[]
end
end
desc do
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
end
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :reassign do |users|
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
users.map(&:id)
else else
@updates[:assignee_id] = nil [users.last.id]
end end
end end
......
...@@ -45,10 +45,13 @@ ...@@ -45,10 +45,13 @@
.panel.panel-danger .panel.panel-danger
.panel-heading Remove group .panel-heading Remove group
.panel-body .panel-body
= form_tag(@group, method: :delete) do
%p %p
Removing group will cause all child projects and resources to be removed. Removing group will cause all child projects and resources to be removed.
%br %br
%strong Removed group can not be restored! %strong Removed group can not be restored!
.form-actions .form-actions
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
= render 'shared/confirm_modal', phrase: @group.path
...@@ -74,7 +74,6 @@ ...@@ -74,7 +74,6 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
- if can_toggle_new_nav?
%li %li
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation") = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider %li.divider
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png" .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id = f.radio_button :color_scheme_id, scheme.id
= scheme.name = scheme.name
- if can_toggle_new_nav?
.col-sm-12 .col-sm-12
%hr %hr
.col-lg-3.profile-settings-sidebar#new-navigation .col-lg-3.profile-settings-sidebar#new-navigation
...@@ -24,8 +23,6 @@ ...@@ -24,8 +23,6 @@
New Navigation New Navigation
%p %p
This setting allows you to turn on or off the new upcoming navigation concept. This setting allows you to turn on or off the new upcoming navigation concept.
= succeed '.' do
= link_to 'Learn more', '', target: '_blank'
.col-lg-9.syntax-theme .col-lg-9.syntax-theme
= label_tag do = label_tag do
.preview= image_tag "old_nav.png" .preview= image_tag "old_nav.png"
......
...@@ -19,10 +19,11 @@ ...@@ -19,10 +19,11 @@
":data-name" => "assignee.name", ":data-name" => "assignee.name",
":data-username" => "assignee.username" } ":data-username" => "assignee.username" }
.dropdown .dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } }, - dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id", ":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee = dropdown_options[:title]
= icon("chevron-down") = icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to") = dropdown_title("Assign to")
......
- @no_container = true - @no_container = true
- page_title "Charts", "Pipelines" - page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs') = page_specific_javascript_bundle_tag('graphs')
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%div{ class: container_class } %div{ class: container_class }
.sub-header-block .sub-header-block
.oneline .oneline
A collection of graphs for Continuous Integration = _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts #charts.ci-charts
.row .row
......
%h4 Overall stats %h4= s_("PipelineCharts|Overall statistics")
%ul %ul
%li %li
Total: = s_("PipelineCharts|Total:")
%strong= pluralize @counts[:total], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li %li
Successful: = s_("PipelineCharts|Successful:")
%strong= pluralize @counts[:success], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li %li
Failed: = s_("PipelineCharts|Failed:")
%strong= pluralize @counts[:failed], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li %li
Success ratio: = s_("PipelineCharts|Success ratio:")
%strong %strong
#{success_ratio(@counts)}% #{success_ratio(@counts)}%
%div %div
%p.light %p.light
Commit duration in minutes for last 30 commits = _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 } %canvas#build_timesChart{ height: 200 }
......
%h4 Pipelines charts %h4= _("Pipelines charts")
%p %p
&nbsp; &nbsp;
%span.cgreen %span.cgreen
= icon("circle") = icon("circle")
success = s_("Pipeline|success")
&nbsp; &nbsp;
%span.cgray %span.cgray
= icon("circle") = icon("circle")
all = s_("Pipeline|all")
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last week = _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)}) (#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 } %canvas#weekChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last month = _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)}) (#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 } %canvas#monthChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last year = _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 } %canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope| - [:week, :month, :year].each do |scope|
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %li.input-token
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } %input.form-control.filtered-search{ search_filter_input_options(type) }
= icon('filter') = icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
......
...@@ -37,19 +37,20 @@ ...@@ -37,19 +37,20 @@
- issuable.assignees.each do |assignee| - issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- title = 'Select assignee' - title = 'Select assignee'
- if issuable.is_a?(Issue) - if issuable.is_a?(Issue)
- unless issuable.assignees.any? - unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data' - options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" } - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true - data[:multi_select] = true
- data['dropdown-title'] = title - data['dropdown-title'] = title
- data['dropdown-header'] = 'Assignee' - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = 1 - data['max-select'] = dropdown_options[:data][:'max-select']
- options[:data].merge!(data) - options[:data].merge!(data)
= dropdown_tag(title, options: options) = dropdown_tag(title, options: options)
...@@ -7,5 +7,5 @@ ...@@ -7,5 +7,5 @@
- if issuable.assignees.length === 0 - if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' } = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false)) = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}" = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
---
title: "Remove group modal like remove project modal (requires typing + confirmation)"
merge_request: 12569
author: Diego Souza
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
---
title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
merge_request: 12578
author: Huang Tao
---
title: Prevent accidental deletion of protected MR source branch by repeating checks
before actual deletion
merge_request:
author:
---
title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
merge_request:
author:
---
title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
merge_request: 11218
author: Stefan Hanreich
---
title: Hide archived project labels from group issue tracker
merge_request: 12547
author: Horacio Bertorello
---
title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
merge_request:
author:
---
title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
merge_request:
author:
---
title: Do not delete protected branches when deleting all merged branches
merge_request: 12624
author:
...@@ -543,6 +543,10 @@ production: &base ...@@ -543,6 +543,10 @@ production: &base
# enabled: true # enabled: true
# host: localhost # host: localhost
# port: 3808 # port: 3808
prometheus:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
# #
# 5. Extra customization # 5. Extra customization
......
...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false ...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
Settings.webpack.dev_server['host'] ||= 'localhost' Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808 Settings.webpack.dev_server['port'] ||= 3808
#
# Prometheus metrics settings
#
Settings['prometheus'] ||= Settingslogic.new({})
Settings.prometheus['unicorn_sampler_interval'] ||= 10
# #
# Testing settings # Testing settings
# #
......
...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation) ...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
end end
# rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/AbcSize
Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware
config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
end
if Gitlab::Metrics.enabled? if Gitlab::Metrics.enabled?
require 'pathname' require 'pathname'
require 'influxdb' require 'influxdb'
...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled? ...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable GC::Profiler.enable
Gitlab::Metrics::Sampler.new.start Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections module TrackNewRedisConnections
def connect(*args) def connect(*args)
......
# Using Docker Images # Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
[Docker Engine](https://www.docker.com/) to test and build any application. [Docker Engine](https://www.docker.com/) to test and build any application.
...@@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all ...@@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all
the commands that we will explore later from your shell, rather than having to the commands that we will explore later from your shell, rather than having to
test them on a dedicated CI server. test them on a dedicated CI server.
## Register docker runner ## Register Docker Runner
To use GitLab Runner with docker you need to register a new runner to use the To use GitLab Runner with Docker you need to [register a new Runner][register]
`docker` executor: to use the `docker` executor.
A one-line example can be seen below:
```bash ```bash
gitlab-ci-multi-runner register \ sudo gitlab-runner register \
--url "https://gitlab.com/" \ --url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "docker-ruby-2.1" \ --description "docker-ruby-2.1" \
--executor "docker" \ --executor "docker" \
...@@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \ ...@@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \
--docker-mysql latest --docker-mysql latest
``` ```
The registered runner will use the `ruby:2.1` docker image and will run two The registered runner will use the `ruby:2.1` Docker image and will run two
services, `postgres:latest` and `mysql:latest`, both of which will be services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process. accessible during the build process.
## What is an image ## What is an image
The `image` keyword is the name of the docker image the docker executor The `image` keyword is the name of the Docker image the Docker executor
will run to perform the CI tasks. will run to perform the CI tasks.
By default the executor will only pull images from [Docker Hub][hub], By default, the executor will only pull images from [Docker Hub][hub],
but this can be configured in the `gitlab-runner/config.toml` by setting but this can be configured in the `gitlab-runner/config.toml` by setting
the [docker pull policy][] to allow using local images. the [Docker pull policy][] to allow using local images.
For more information about images and Docker Hub please read For more information about images and Docker Hub please read
the [Docker Fundamentals][] documentation. the [Docker Fundamentals][] documentation.
## What is a service ## What is a service
The `services` keyword defines just another docker image that is run during The `services` keyword defines just another Docker image that is run during
your job and is linked to the docker image that the `image` keyword defines. your job and is linked to the Docker image that the `image` keyword defines.
This allows you to access the service image during build time. This allows you to access the service image during build time.
The service image can run any application, but the most common use case is to The service image can run any application, but the most common use case is to
...@@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an ...@@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an
existing image and run it as an additional container than install `mysql` every existing image and run it as an additional container than install `mysql` every
time the project is built. time the project is built.
You are not limited to have only database services. You can add as many
services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
Any image found at [Docker Hub][hub] or your private Container Registry can be
used as a service.
You can see some widely used services examples in the relevant documentation of You can see some widely used services examples in the relevant documentation of
[CI services examples](../services/README.md). [CI services examples](../services/README.md).
...@@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container. ...@@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container.
The service container for MySQL will be accessible under the hostname `mysql`. The service container for MySQL will be accessible under the hostname `mysql`.
So, in order to access your database service you have to connect to the host So, in order to access your database service you have to connect to the host
named `mysql` instead of a socket or `localhost`. named `mysql` instead of a socket or `localhost`. Read more in [accessing the
services](#accessing-the-services).
### Accessing the services
Let's say that you need a Wordpress instance to test some API integration with
your application.
## Overwrite image and services You can then use for example the [tutum/wordpress][] image in your
`.gitlab-ci.yml`:
See [How to use other images as services](#how-to-use-other-images-as-services). ```yaml
services:
- tutum/wordpress:latest
```
## How to use other images as services If you don't [specify a service alias](#available-settings-for-services-entry),
when the job is run, `tutum/wordpress` will be started and you will have
access to it from your build container under two hostnames to choose from:
You are not limited to have only database services. You can add as many - `tutum-wordpress`
services you need to `.gitlab-ci.yml` or manually modify `config.toml`. - `tutum__wordpress`
Any image found at [Docker Hub][hub] can be used as a service.
## Define image and services from `.gitlab-ci.yml` >**Note:**
Hostnames with underscores are not RFC valid and may cause problems in 3rd party
applications.
The default aliases for the service's hostname are created from its image name
following these rules:
- Everything after the colon (`:`) is stripped
- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
is created
- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
created (requires GitLab Runner v1.1.0 or higher)
To override the default behavior, you can
[specify a service alias](#available-settings-for-services-entry).
## Define `image` and `services` from `.gitlab-ci.yml`
You can simply define an image that will be used for all jobs and a list of You can simply define an image that will be used for all jobs and a list of
services that you want to use during build time. services that you want to use during build time:
```yaml ```yaml
image: ruby:2.2 image: ruby:2.2
...@@ -125,6 +159,203 @@ test:2.2: ...@@ -125,6 +159,203 @@ test:2.2:
- bundle exec rake spec - bundle exec rake spec
``` ```
Or you can pass some [extended configuration options](#extended-docker-configuration-options)
for `image` and `services`:
```yaml
image:
name: ruby:2.2
entrypoint: ["/bin/bash"]
services:
- name: my-postgres:9.4
alias: db-postgres
entrypoint: ["/usr/local/bin/db-postgres"]
command: ["start"]
before_script:
- bundle install
test:
script:
- bundle exec rake spec
```
## Extended Docker configuration options
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
When configuring the `image` or `services` entries, you can use a string or a map as
options:
- when using a string as an option, it must be the full name of the image to use
(including the Registry part if you want to download the image from a Registry
other than Docker Hub)
- when using a map as an option, then it must contain at least the `name`
option, which is the same name of the image as used for the string setting
For example, the following two definitions are equal:
1. Using a string as an option to `image` and `services`:
```yaml
image: "registry.example.com/my/image:latest"
services:
- postgresql:9.4
- redis:latest
```
1. Using a map as an option to `image` and `services`. The use of `image:name` is
required:
```yaml
image:
name: "registry.example.com/my/image:latest"
services:
- name: postgresql:9.4
- name: redis:latest
```
### Available settings for `image`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
### Available settings for `services`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
| `command` | no | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
| `alias` | no | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
### Starting multiple services from the same image
Before the new extended Docker configuration options, the following configuration
would not work properly:
```yaml
services:
- mysql:latest
- mysql:latest
```
The Runner would start two containers using the `mysql:latest` image, but both
of them would be added to the job's container with the `mysql` alias based on
the [default hostname naming](#accessing-the-services). This would end with one
of the services not being accessible.
After the new extended Docker configuration options, the above example would
look like:
```yaml
services:
- name: mysql:latest
alias: mysql-1
- name: mysql:latest
alias: mysql-2
```
The Runner will still start two containers using the `mysql:latest` image,
but now each of them will also be accessible with the alias configured
in `.gitlab-ci.yml` file.
### Setting a command for the service
Let's assume you have a `super/sql:latest` image with some SQL database
inside it and you would like to use it as a service for your job. Let's also
assume that this image doesn't start the database process while starting
the container and the user needs to manually use `/usr/bin/super-sql run` as
a command to start the database.
Before the new extended Docker configuration options, you would need to create
your own image based on the `super/sql:latest` image, add the default command,
and then use it in job's configuration, like:
```Dockerfile
# my-super-sql:latest image's Dockerfile
FROM super/sql:latest
CMD ["/usr/bin/super-sql", "run"]
```
```yaml
# .gitlab-ci.yml
services:
- my-super-sql:latest
```
After the new extended Docker configuration options, you can now simply
set a `command` in `.gitlab-ci.yml`, like:
```yaml
# .gitlab-ci.yml
services:
- name: super/sql:latest
command: ["/usr/bin/super-sql", "run"]
```
As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
### Overriding the entrypoint of an image
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
this image is configured with `/usr/bin/super-sql run` as an entrypoint. That
means, that when starting the container without additional options, it will run
the database's process, while Runner expects that the image will have no
entrypoint or at least will start with a shell as its entrypoint.
Previously we would need to create our own image based on the
`super/sql:experimental` image, set the entrypoint to a shell, and then use
it in job's configuration, e.g.:
Before the new extended Docker configuration options, you would need to create
your own image based on the `super/sql:experimental` image, set the entrypoint
to a shell and then use it in job's configuration, like:
```Dockerfile
# my-super-sql:experimental image's Dockerfile
FROM super/sql:experimental
ENTRYPOINT ["/bin/sh"]
```
```yaml
# .gitlab-ci.yml
image: my-super-sql:experimental
```
After the new extended Docker configuration options, you can now simply
set an `entrypoint` in `.gitlab-ci.yml`, like:
```yaml
# .gitlab-ci.yml
image:
name: super/sql:experimental
entrypoint: ["/bin/sh"]
```
As you can see the syntax of `entrypoint` is similar to
[Dockerfile's `ENTRYPOINT`][entrypoint].
## Define image and services in `config.toml` ## Define image and services in `config.toml`
Look for the `[runners.docker]` section: Look for the `[runners.docker]` section:
...@@ -138,7 +369,7 @@ Look for the `[runners.docker]` section: ...@@ -138,7 +369,7 @@ Look for the `[runners.docker]` section:
The image and services defined this way will be added to all job run by The image and services defined this way will be added to all job run by
that runner. that runner.
## Define an image from a private Docker registry ## Define an image from a private Container Registry
> **Notes:** > **Notes:**
- This feature requires GitLab Runner **1.8** or higher - This feature requires GitLab Runner **1.8** or higher
...@@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps: ...@@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps:
You can add configuration for as many registries as you want, adding more You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above. registries to the `"auths"` hash as described above.
## Accessing the services
Let's say that you need a Wordpress instance to test some API integration with
your application.
You can then use for example the [tutum/wordpress][] image in your
`.gitlab-ci.yml`:
```yaml
services:
- tutum/wordpress:latest
```
When the job is run, `tutum/wordpress` will be started and you will have
access to it from your build container under the hostnames `tutum-wordpress`
(requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`.
When using a private registry, the image name also includes a hostname and port
of the registry.
```yaml
services:
- docker.example.com:5000/wordpress:latest
```
The service hostname will also include the registry hostname. Service will be
available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer)
and `docker.example.com__wordpress`.
*Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.*
The alias hostnames for the service are made from the image name following these
rules:
1. Everything after `:` is stripped
2. Slash (`/`) is replaced with double underscores (`__`) - primary alias
3. Slash (`/`) is replaced with dash (`-`) - secondary alias, requires GitLab Runner v1.1.0 or newer
## Configuring services ## Configuring services
Many services accept environment variables which allow you to easily change Many services accept environment variables which allow you to easily change
...@@ -257,7 +450,7 @@ See the specific documentation for ...@@ -257,7 +450,7 @@ See the specific documentation for
## How Docker integration works ## How Docker integration works
Below is a high level overview of the steps performed by docker during job Below is a high level overview of the steps performed by Docker during job
time. time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`. 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
...@@ -274,7 +467,7 @@ time. ...@@ -274,7 +467,7 @@ time.
## How to debug a job locally ## How to debug a job locally
*Note: The following commands are run without root privileges. You should be *Note: The following commands are run without root privileges. You should be
able to run docker with your regular user account.* able to run Docker with your regular user account.*
First start with creating a file named `build_script`: First start with creating a file named `build_script`:
...@@ -334,3 +527,6 @@ creation. ...@@ -334,3 +527,6 @@ creation.
[mysql-hub]: https://hub.docker.com/r/_/mysql/ [mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry [runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
[secret variable]: ../variables/README.md#secret-variables [secret variable]: ../variables/README.md#secret-variables
[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
[register]: https://docs.gitlab.com/runner/register/
...@@ -37,7 +37,7 @@ future GitLab releases.** ...@@ -37,7 +37,7 @@ future GitLab releases.**
|-------------------------------- |--------|--------|-------------| |-------------------------------- |--------|--------|-------------|
| **CI** | all | 0.4 | Mark that job is executed in CI environment | | **CI** | all | 0.4 | Mark that job is executed in CI environment |
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
......
...@@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_ ...@@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`; mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Grant the GitLab user necessary permissions on the database # Grant the GitLab user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session # Quit the database session
mysql> \q mysql> \q
......
...@@ -156,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) ...@@ -156,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make sudo -u git -H make
``` ```
### 10. Update configuration files ### 10. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
```bash
mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
```
### 11. Update configuration files
#### New configuration options for `gitlab.yml` #### New configuration options for `gitlab.yml`
...@@ -230,7 +239,7 @@ For Ubuntu 16.04.1 LTS: ...@@ -230,7 +239,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload sudo systemctl daemon-reload
``` ```
### 11. Install libs, migrations, etc. ### 12. Install libs, migrations, etc.
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -256,14 +265,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ...@@ -256,14 +265,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
### 12. Start application ### 13. Start application
```bash ```bash
sudo service gitlab start sudo service gitlab start
sudo service nginx restart sudo service nginx restart
``` ```
### 13. Check application status ### 14. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
...@@ -67,7 +67,7 @@ module API ...@@ -67,7 +67,7 @@ module API
result = ::Files::MultiService.new(user_project, current_user, attrs).execute result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success if result[:status] == :success
commit_detail = user_project.repository.commits(result[:result], limit: 1).first commit_detail = user_project.repository.commit(result[:result])
present commit_detail, with: Entities::RepoCommitDetail present commit_detail, with: Entities::RepoCommitDetail
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
......
...@@ -4,10 +4,13 @@ module API ...@@ -4,10 +4,13 @@ module API
before do before do
allow_access_with_scope :read_user if request.get? allow_access_with_scope :read_user if request.get?
authenticate!
end end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do helpers do
def find_user(params) def find_user(params)
id = params[:user_id] || params[:id] id = params[:user_id] || params[:id]
...@@ -51,15 +54,22 @@ module API ...@@ -51,15 +54,22 @@ module API
use :pagination use :pagination
end end
get do get do
unless can?(current_user, :read_users_list)
render_api_error!("Not authorized.", 403)
end
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic authorized = can?(current_user, :read_users_list)
# When `current_user` is not present, require that the `username`
# parameter is passed, to prevent an unauthenticated user from accessing
# a list of all the users on the GitLab instance. `UsersFinder` performs
# an exact match on the `username` parameter, so we are guaranteed to
# get either 0 or 1 `users` here.
authorized &&= params[:username].present? if current_user.blank?
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity present paginate(users), with: entity
end end
...@@ -398,6 +408,10 @@ module API ...@@ -398,6 +408,10 @@ module API
end end
resource :user do resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserPublic success Entities::UserPublic
end end
......
...@@ -34,7 +34,7 @@ module Gitlab ...@@ -34,7 +34,7 @@ module Gitlab
commit_id: commit_id, commit_id: commit_id,
prefix: ref_prefix prefix: ref_prefix
) )
GitalyClient.call(@storage, :ref, :find_ref_name, request).name encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup)
end end
def count_tag_names def count_tag_names
......
...@@ -12,7 +12,8 @@ module Gitlab ...@@ -12,7 +12,8 @@ module Gitlab
'zh_HK' => '繁體中文(香港)', 'zh_HK' => '繁體中文(香港)',
'zh_TW' => '繁體中文(臺灣)', 'zh_TW' => '繁體中文(臺灣)',
'bg' => 'български', 'bg' => 'български',
'eo' => 'Esperanto' 'eo' => 'Esperanto',
'it' => 'Italiano'
}.freeze }.freeze
def available_locales def available_locales
......
require 'logger'
module Gitlab
module Metrics
class BaseSampler
def self.initialize_instance(*args)
raise "#{name} singleton instance already initialized" if @instance
@instance = new(*args)
at_exit(&@instance.method(:stop))
@instance
end
def self.instance
@instance
end
attr_reader :running
# interval - The sampling interval in seconds.
def initialize(interval)
interval_half = interval.to_f / 2
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@mutex = Mutex.new
end
def enabled?
true
end
def start
return unless enabled?
@mutex.synchronize do
return if running
@running = true
@thread = Thread.new do
sleep(sleep_interval)
while running
safe_sample
sleep(sleep_interval)
end
end
end
end
def stop
@mutex.synchronize do
return unless running
@running = false
if @thread
@thread.wakeup if @thread.alive?
@thread.join
@thread = nil
end
end
end
def safe_sample
sample
rescue => e
Rails.logger.warn("#{self.class}: #{e}, stopping")
stop
end
def sample
raise NotImplementedError
end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end
end
end
module Gitlab
module Metrics
class ConnectionRackMiddleware
def initialize(app)
@app = app
end
def self.rack_request_count
@rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
end
def self.rack_response_count
@rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
end
def self.rack_uncaught_errors_count
@rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
end
def self.rack_execution_time
@rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
{}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
end
def call(env)
method = env['REQUEST_METHOD'].downcase
started = Time.now.to_f
begin
ConnectionRackMiddleware.rack_request_count.increment(method: method)
status, headers, body = @app.call(env)
ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
[status, headers, body]
rescue
ConnectionRackMiddleware.rack_uncaught_errors_count.increment
raise
ensure
elapsed = Time.now.to_f - started
ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
end
end
end
end
end
...@@ -5,13 +5,10 @@ module Gitlab ...@@ -5,13 +5,10 @@ module Gitlab
# This class is used to gather statistics that can't be directly associated # This class is used to gather statistics that can't be directly associated
# with a transaction such as system memory usage, garbage collection # with a transaction such as system memory usage, garbage collection
# statistics, etc. # statistics, etc.
class Sampler class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds. # interval - The sampling interval in seconds.
def initialize(interval = Metrics.settings[:sample_interval]) def initialize(interval = Metrics.settings[:sample_interval])
interval_half = interval.to_f / 2 super(interval)
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@last_step = nil @last_step = nil
@metrics = [] @metrics = []
...@@ -26,18 +23,6 @@ module Gitlab ...@@ -26,18 +23,6 @@ module Gitlab
end end
end end
def start
Thread.new do
Thread.current.abort_on_exception = true
loop do
sleep(sleep_interval)
sample
end
end
end
def sample def sample
sample_memory_usage sample_memory_usage
sample_file_descriptors sample_file_descriptors
...@@ -111,23 +96,6 @@ module Gitlab ...@@ -111,23 +96,6 @@ module Gitlab
def sidekiq? def sidekiq?
Sidekiq.server? Sidekiq.server?
end end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end end
end end
end end
...@@ -29,8 +29,8 @@ module Gitlab ...@@ -29,8 +29,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels) provide_metric(name) || registry.summary(name, docstring, base_labels)
end end
def gauge(name, docstring, base_labels = {}) def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
provide_metric(name) || registry.gauge(name, docstring, base_labels) provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
......
module Gitlab
module Metrics
class UnicornSampler < BaseSampler
def initialize(interval)
super(interval)
end
def unicorn_active_connections
@unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
end
def unicorn_queued_connections
@unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
end
def enabled?
# Raindrops::Linux.tcp_listener_stats is only present on Linux
unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
end
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
end
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
end
end
private
def tcp_listeners
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
end
end
end
...@@ -17,9 +17,27 @@ msgstr "" ...@@ -17,9 +17,27 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n" "\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}" msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "" msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
...@@ -61,9 +79,24 @@ msgstr[1] "" ...@@ -61,9 +79,24 @@ msgstr[1] ""
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr "" msgstr ""
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr ""
msgid "BranchSwitcherTitle|Switch branch"
msgstr ""
msgid "Branches" msgid "Branches"
msgstr "" msgstr ""
msgid "Browse Directory"
msgstr ""
msgid "Browse File"
msgstr ""
msgid "Browse Files"
msgstr ""
msgid "Browse files" msgid "Browse files"
msgstr "" msgstr ""
...@@ -159,6 +192,9 @@ msgid_plural "Commits" ...@@ -159,6 +192,9 @@ msgid_plural "Commits"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message" msgid "Commit message"
msgstr "" msgstr ""
...@@ -171,6 +207,9 @@ msgstr "" ...@@ -171,6 +207,9 @@ msgstr ""
msgid "Commits" msgid "Commits"
msgstr "" msgstr ""
msgid "Commits feed"
msgstr ""
msgid "Commits|History" msgid "Commits|History"
msgstr "" msgstr ""
...@@ -195,6 +234,9 @@ msgstr "" ...@@ -195,6 +234,9 @@ msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
msgstr "" msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory" msgid "Create directory"
msgstr "" msgstr ""
...@@ -213,6 +255,9 @@ msgstr "" ...@@ -213,6 +255,9 @@ msgstr ""
msgid "CreateTag|Tag" msgid "CreateTag|Tag"
msgstr "" msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone" msgid "Cron Timezone"
msgstr "" msgstr ""
...@@ -323,6 +368,9 @@ msgstr "" ...@@ -323,6 +368,9 @@ msgstr ""
msgid "Files" msgid "Files"
msgstr "" msgstr ""
msgid "Filter by commit message"
msgstr ""
msgid "Find by path" msgid "Find by path"
msgstr "" msgstr ""
...@@ -370,6 +418,15 @@ msgstr "" ...@@ -370,6 +418,15 @@ msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Cycle Analytics"
msgstr "" msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled" msgid "LFSStatus|Disabled"
msgstr "" msgstr ""
...@@ -535,6 +592,21 @@ msgstr "" ...@@ -535,6 +592,21 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated" msgid "PipelineSchedules|Activated"
msgstr "" msgstr ""
...@@ -565,6 +637,18 @@ msgstr "" ...@@ -565,6 +637,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom" msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "" msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage" msgid "Pipeline|with stage"
msgstr "" msgstr ""
...@@ -688,7 +772,7 @@ msgstr "" ...@@ -688,7 +772,7 @@ msgstr ""
msgid "Select target branch" msgid "Select target branch"
msgstr "" msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}" msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
msgid "Set up CI" msgid "Set up CI"
...@@ -717,9 +801,6 @@ msgstr "" ...@@ -717,9 +801,6 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr "" msgstr ""
msgid "Start a <strong>new merge request</strong> with these changes"
msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
msgstr "" msgstr ""
...@@ -948,9 +1029,15 @@ msgstr "" ...@@ -948,9 +1029,15 @@ msgstr ""
msgid "Upload file" msgid "Upload file"
msgstr "" msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
msgid "Use your global notification setting" msgid "Use your global notification setting"
msgstr "" msgstr ""
msgid "View open merge request"
msgstr ""
msgid "VisibilityLevel|Internal" msgid "VisibilityLevel|Internal"
msgstr "" msgstr ""
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-19 15:50-0500\n" "POT-Creation-Date: 2017-06-28 13:32+0200\n"
"PO-Revision-Date: 2017-06-19 15:50-0500\n" "PO-Revision-Date: 2017-06-28 13:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -18,7 +18,7 @@ msgstr "" ...@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid "%d additional commit have been omitted to prevent performance issues." msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues." msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
...@@ -31,6 +31,14 @@ msgstr[1] "" ...@@ -31,6 +31,14 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}" msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "" msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
...@@ -185,6 +193,9 @@ msgid_plural "Commits" ...@@ -185,6 +193,9 @@ msgid_plural "Commits"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message" msgid "Commit message"
msgstr "" msgstr ""
...@@ -224,6 +235,9 @@ msgstr "" ...@@ -224,6 +235,9 @@ msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
msgstr "" msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory" msgid "Create directory"
msgstr "" msgstr ""
...@@ -242,6 +256,9 @@ msgstr "" ...@@ -242,6 +256,9 @@ msgstr ""
msgid "CreateTag|Tag" msgid "CreateTag|Tag"
msgstr "" msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone" msgid "Cron Timezone"
msgstr "" msgstr ""
...@@ -402,6 +419,15 @@ msgstr "" ...@@ -402,6 +419,15 @@ msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Cycle Analytics"
msgstr "" msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled" msgid "LFSStatus|Disabled"
msgstr "" msgstr ""
...@@ -567,6 +593,21 @@ msgstr "" ...@@ -567,6 +593,21 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated" msgid "PipelineSchedules|Activated"
msgstr "" msgstr ""
...@@ -597,6 +638,18 @@ msgstr "" ...@@ -597,6 +638,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom" msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "" msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage" msgid "Pipeline|with stage"
msgstr "" msgstr ""
...@@ -720,7 +773,7 @@ msgstr "" ...@@ -720,7 +773,7 @@ msgstr ""
msgid "Select target branch" msgid "Select target branch"
msgstr "" msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}" msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
msgid "Set up CI" msgid "Set up CI"
......
# Huang Tao <htve@outlook.com>, 2017. #zanata
# Paolo Falomo <info@paolofalomo.it>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-15 21:59-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-02 10:32-0400\n"
"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
"Language-Team: Italian\n"
"Language: it\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} ha committato %{commit_timeago}"
msgid "About auto deploy"
msgstr "Riguardo il rilascio automatico"
msgid "Active"
msgstr "Attivo"
msgid "Activity"
msgstr "Attività"
msgid "Add Changelog"
msgstr "Aggiungi Changelog"
msgid "Add Contribution guide"
msgstr "Aggiungi Guida per contribuire"
msgid "Add License"
msgstr "Aggiungi Licenza"
msgid "Add an SSH key to your profile to pull or push via SSH."
msgstr ""
"Aggiungi una chiave SSH al tuo profilo per eseguire pull o push tramite SSH"
msgid "Add new directory"
msgstr "Aggiungi una directory (cartella)"
msgid "Archived project! Repository is read-only"
msgstr "Progetto archiviato! La Repository è sola-lettura"
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Sei sicuro di voler cancellare questa pipeline programmata?"
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
"Aggiungi un file tramite trascina &amp; rilascia ( drag &amp; drop) o "
"%{upload_link}"
msgid "Branch"
msgid_plural "Branches"
msgstr[0] "Branch"
msgstr[1] "Branches"
msgid ""
"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
"choose a GitLab CI Yaml template and commit your changes. "
"%{link_to_autodeploy_doc}"
msgstr ""
"La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un "
"rilascio automatico scegli un template CI di Gitlab e committa le tue "
"modifiche %{link_to_autodeploy_doc}"
msgid "Branches"
msgstr "Branches"
msgid "Browse files"
msgstr "Guarda i files"
msgid "ByAuthor|by"
msgstr "per"
msgid "CI configuration"
msgstr "Configurazione CI (Integrazione Continua)"
msgid "Cancel"
msgstr "Cancella"
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr "Preleva nella branch"
msgid "ChangeTypeActionLabel|Revert in branch"
msgstr "Ripristina nella branch"
msgid "ChangeTypeAction|Cherry-pick"
msgstr "Cherry-pick"
msgid "ChangeTypeAction|Revert"
msgstr "Ripristina"
msgid "Changelog"
msgstr "Changelog"
msgid "Charts"
msgstr "Grafici"
msgid "Cherry-pick this commit"
msgstr "Cherry-pick this commit"
msgid "Cherry-pick this merge request"
msgstr "Cherry-pick questa richiesta di merge"
msgid "CiStatusLabel|canceled"
msgstr "cancellato"
msgid "CiStatusLabel|created"
msgstr "creato"
msgid "CiStatusLabel|failed"
msgstr "fallito"
msgid "CiStatusLabel|manual action"
msgstr "azione manuale"
msgid "CiStatusLabel|passed"
msgstr "superata"
msgid "CiStatusLabel|passed with warnings"
msgstr "superata con avvisi"
msgid "CiStatusLabel|pending"
msgstr "in coda"
msgid "CiStatusLabel|skipped"
msgstr "saltata"
msgid "CiStatusLabel|waiting for manual action"
msgstr "in attesa di azione manuale"
msgid "CiStatusText|blocked"
msgstr "bloccata"
msgid "CiStatusText|canceled"
msgstr "cancellata"
msgid "CiStatusText|created"
msgstr "creata"
msgid "CiStatusText|failed"
msgstr "fallita"
msgid "CiStatusText|manual"
msgstr "manuale"
msgid "CiStatusText|passed"
msgstr "superata"
msgid "CiStatusText|pending"
msgstr "in coda"
msgid "CiStatusText|skipped"
msgstr "saltata"
msgid "CiStatus|running"
msgstr "in corso"
msgid "Commit"
msgid_plural "Commits"
msgstr[0] "Commit"
msgstr[1] "Commits"
msgid "Commit message"
msgstr "Messaggio del commit"
msgid "CommitBoxTitle|Commit"
msgstr "Commit"
msgid "CommitMessage|Add %{file_name}"
msgstr "Aggiungi %{file_name}"
msgid "Commits"
msgstr "Commits"
msgid "Commits|History"
msgstr "Cronologia"
msgid "Committed by"
msgstr "Committato da "
msgid "Compare"
msgstr "Confronta"
msgid "Contribution guide"
msgstr "Guida per contribuire"
msgid "Contributors"
msgstr "Collaboratori"
msgid "Copy URL to clipboard"
msgstr "Copia URL negli appunti"
msgid "Copy commit SHA to clipboard"
msgstr "Copia l'SHA del commit negli appunti"
msgid "Create New Directory"
msgstr "Crea una nuova cartella"
msgid "Create directory"
msgstr "Crea cartella"
msgid "Create empty bare repository"
msgstr "Crea una repository vuota"
msgid "Create merge request"
msgstr "Crea una richiesta di merge"
msgid "Create new..."
msgstr "Crea nuovo..."
msgid "CreateNewFork|Fork"
msgstr "Fork"
msgid "CreateTag|Tag"
msgstr "Tag"
msgid "Cron Timezone"
msgstr "Cron Timezone"
msgid "Cron syntax"
msgstr "Sintassi Cron"
msgid "Custom notification events"
msgstr "Eventi-Notifica personalizzati"
msgid ""
"Custom notification levels are the same as participating levels. With custom "
"notification levels you will also receive notifications for select events. "
"To find out more, check out %{notification_link}."
msgstr ""
"I livelli di notifica personalizzati sono uguali a quelli di partecipazione. "
"Con i livelli di notifica personalizzati riceverai anche notifiche per gli "
"eventi da te scelti %{notification_link}."
msgid "Cycle Analytics"
msgstr "Statistiche Cicliche"
msgid ""
"Cycle Analytics gives an overview of how much time it takes to go from idea "
"to production in your project."
msgstr ""
"L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea "
"ed il rilascio in produzione del tuo progetto"
msgid "CycleAnalyticsStage|Code"
msgstr "Codice"
msgid "CycleAnalyticsStage|Issue"
msgstr "Issue"
msgid "CycleAnalyticsStage|Plan"
msgstr "Pianificazione"
msgid "CycleAnalyticsStage|Production"
msgstr "Produzione"
msgid "CycleAnalyticsStage|Review"
msgstr "Revisione"
msgid "CycleAnalyticsStage|Staging"
msgstr "Pre-rilascio"
msgid "CycleAnalyticsStage|Test"
msgstr "Test"
msgid "Define a custom pattern with cron syntax"
msgstr "Definisci un patter personalizzato mediante la sintassi cron"
msgid "Delete"
msgstr "Elimina"
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "Rilascio"
msgstr[1] "Rilasci"
msgid "Description"
msgstr "Descrizione"
msgid "Directory name"
msgstr "Nome cartella"
msgid "Don't show again"
msgstr "Non mostrare più"
msgid "Download"
msgstr "Scarica"
msgid "Download tar"
msgstr "Scarica tar"
msgid "Download tar.bz2"
msgstr "Scarica tar.bz2"
msgid "Download tar.gz"
msgstr "Scarica tar.gz"
msgid "Download zip"
msgstr "Scarica zip"
msgid "DownloadArtifacts|Download"
msgstr "Scarica"
msgid "DownloadCommit|Email Patches"
msgstr "Email Patches"
msgid "DownloadCommit|Plain Diff"
msgstr "Differenze"
msgid "DownloadSource|Download"
msgstr "Scarica"
msgid "Edit"
msgstr "Modifica"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Cambia programmazione della pipeline %{id}"
msgid "Every day (at 4:00am)"
msgstr "Ogni giorno (alle 4 del mattino)"
msgid "Every month (on the 1st at 4:00am)"
msgstr "Ogni primo giorno del mese (alle 4 del mattino)"
msgid "Every week (Sundays at 4:00am)"
msgstr "Ogni settimana (Di domenica alle 4 del mattino)"
msgid "Failed to change the owner"
msgstr "Impossibile cambiare owner"
msgid "Failed to remove the pipeline schedule"
msgstr "Impossibile rimuovere la pipeline pianificata"
msgid "Files"
msgstr "Files"
msgid "Find by path"
msgstr "Trova in percorso"
msgid "Find file"
msgstr "Trova file"
msgid "FirstPushedBy|First"
msgstr "Primo"
msgid "FirstPushedBy|pushed by"
msgstr "Push di"
msgid "Fork"
msgid_plural "Forks"
msgstr[0] "Fork"
msgstr[1] "Forks"
msgid "ForkedFromProjectPath|Forked from"
msgstr "Fork da"
msgid "From issue creation until deploy to production"
msgstr "Dalla creazione di un issue fino al rilascio in produzione"
msgid "From merge request merge until deploy to production"
msgstr ""
"Dalla richiesta di merge fino effettua il merge fino al rilascio in "
"produzione"
msgid "Go to your fork"
msgstr "Vai il tuo fork"
msgid "GoToYourFork|Fork"
msgstr "Fork"
msgid "Home"
msgstr "Home"
msgid "Housekeeping successfully started"
msgstr "Housekeeping iniziato con successo"
msgid "Import repository"
msgstr "Importa repository"
msgid "Interval Pattern"
msgstr "Intervallo di Pattern"
msgid "Introducing Cycle Analytics"
msgstr "Introduzione delle Analisi Cicliche"
msgid "LFSStatus|Disabled"
msgstr "Disabilitato"
msgid "LFSStatus|Enabled"
msgstr "Abilitato"
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "L'ultimo %d giorno"
msgstr[1] "Gli ultimi %d giorni"
msgid "Last Pipeline"
msgstr "Ultima Pipeline"
msgid "Last Update"
msgstr "Ultimo Aggiornamento"
msgid "Last commit"
msgstr "Ultimo Commit"
msgid "Learn more in the"
msgstr "Leggi di più su"
msgid "Learn more in the|pipeline schedules documentation"
msgstr "documentazione sulla pianificazione delle pipelines"
msgid "Leave group"
msgstr "Abbandona il gruppo"
msgid "Leave project"
msgstr "Abbandona il progetto"
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limita visualizzazione %d d'evento"
msgstr[1] "Limita visualizzazione %d di eventi"
msgid "Median"
msgstr "Mediano"
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "aggiungi una chiave SSH"
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "Nuovo Issue"
msgstr[1] "Nuovi Issues"
msgid "New Pipeline Schedule"
msgstr "Nuova pianificazione Pipeline"
msgid "New branch"
msgstr "Nuova Branch"
msgid "New directory"
msgstr "Nuova directory"
msgid "New file"
msgstr "Nuovo file"
msgid "New issue"
msgstr "Nuovo Issue"
msgid "New merge request"
msgstr "Nuova richiesta di merge"
msgid "New schedule"
msgstr "Nuova pianficazione"
msgid "New snippet"
msgstr "Nuovo snippet"
msgid "New tag"
msgstr "Nuovo tag"
msgid "No repository"
msgstr "Nessuna Repository"
msgid "No schedules"
msgstr "Nessuna pianificazione"
msgid "Not available"
msgstr "Non disponibile"
msgid "Not enough data"
msgstr "Dati insufficienti "
msgid "Notification events"
msgstr "Notifica eventi"
msgid "NotificationEvent|Close issue"
msgstr "Chiudi issue"
msgid "NotificationEvent|Close merge request"
msgstr "Chiudi richiesta di merge"
msgid "NotificationEvent|Failed pipeline"
msgstr "Pipeline fallita"
msgid "NotificationEvent|Merge merge request"
msgstr "Completa la richiesta di merge"
msgid "NotificationEvent|New issue"
msgstr "Nuovo issue"
msgid "NotificationEvent|New merge request"
msgstr "Nuova richiesta di merge"
msgid "NotificationEvent|New note"
msgstr "Nuova nota"
msgid "NotificationEvent|Reassign issue"
msgstr "Riassegna issue"
msgid "NotificationEvent|Reassign merge request"
msgstr "Riassegna richiesta di Merge"
msgid "NotificationEvent|Reopen issue"
msgstr "Riapri issue"
msgid "NotificationEvent|Successful pipeline"
msgstr "Pipeline Completata"
msgid "NotificationLevel|Custom"
msgstr "Personalizzato"
msgid "NotificationLevel|Disabled"
msgstr "Disabilitato"
msgid "NotificationLevel|Global"
msgstr "Globale"
msgid "NotificationLevel|On mention"
msgstr "Se menzionato"
msgid "NotificationLevel|Participate"
msgstr "Partecipa"
msgid "NotificationLevel|Watch"
msgstr "Osserva"
msgid "OfSearchInADropdown|Filter"
msgstr "Filtra"
msgid "OpenedNDaysAgo|Opened"
msgstr "Aperto"
msgid "Options"
msgstr "Opzioni"
msgid "Owner"
msgstr "Owner"
msgid "Pipeline"
msgstr "Pipeline"
msgid "Pipeline Health"
msgstr "Stato della Pipeline"
msgid "Pipeline Schedule"
msgstr "Pianificazione Pipeline"
msgid "Pipeline Schedules"
msgstr "Pianificazione multipla Pipeline"
msgid "PipelineSchedules|Activated"
msgstr "Attivata"
msgid "PipelineSchedules|Active"
msgstr "Attiva"
msgid "PipelineSchedules|All"
msgstr "Tutto"
msgid "PipelineSchedules|Inactive"
msgstr "Inattiva"
msgid "PipelineSchedules|Next Run"
msgstr "Prossima esecuzione"
msgid "PipelineSchedules|None"
msgstr "Nessuna"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Fornisci una breve descrizione per questa pipeline"
msgid "PipelineSchedules|Take ownership"
msgstr "Prendi possesso"
msgid "PipelineSchedules|Target"
msgstr "Target"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizzato"
msgid "Pipeline|with stage"
msgstr "con stadio"
msgid "Pipeline|with stages"
msgstr "con più stadi"
msgid "Project '%{project_name}' queued for deletion."
msgstr "Il Progetto '%{project_name}' in coda di eliminazione."
msgid "Project '%{project_name}' was successfully created."
msgstr "Il Progetto '%{project_name}' è stato creato con successo."
msgid "Project '%{project_name}' was successfully updated."
msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo."
msgid "Project '%{project_name}' will be deleted."
msgstr "Il Progetto '%{project_name}' verrà eliminato"
msgid "Project access must be granted explicitly to each user."
msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente"
msgid "Project export could not be deleted."
msgstr "L'esportazione del progetto non può essere eliminata."
msgid "Project export has been deleted."
msgstr "L'esportazione del progetto è stata eliminata."
msgid ""
"Project export link has expired. Please generate a new export from your "
"project settings."
msgstr ""
"Il link d'esportazione del progetto è scaduto. Genera una nuova esportazione "
"dalle impostazioni del tuo progetto."
msgid "Project export started. A download link will be sent by email."
msgstr ""
"Esportazione del progetto iniziata. Un link di download sarà inviato via "
"email."
msgid "Project home"
msgstr "Home di progetto"
msgid "ProjectFeature|Disabled"
msgstr "Disabilitato"
msgid "ProjectFeature|Everyone with access"
msgstr "Chiunque con accesso"
msgid "ProjectFeature|Only team members"
msgstr "Solo i membri del team"
msgid "ProjectFileTree|Name"
msgstr "Nome"
msgid "ProjectLastActivity|Never"
msgstr "Mai"
msgid "ProjectLifecycle|Stage"
msgstr "Stadio"
msgid "ProjectNetworkGraph|Graph"
msgstr "Grafico"
msgid "Read more"
msgstr "Continua..."
msgid "Readme"
msgstr "Leggimi"
msgid "RefSwitcher|Branches"
msgstr "Branches"
msgid "RefSwitcher|Tags"
msgstr "Tags"
msgid "Related Commits"
msgstr "Commit correlati"
msgid "Related Deployed Jobs"
msgstr "Attività di Rilascio Correlate"
msgid "Related Issues"
msgstr "Issues Correlati"
msgid "Related Jobs"
msgstr "Attività Correlate"
msgid "Related Merge Requests"
msgstr "Richieste di Merge Correlate"
msgid "Related Merged Requests"
msgstr "Richieste di Merge Completate Correlate"
msgid "Remind later"
msgstr "Ricordamelo più tardi"
msgid "Remove project"
msgstr "Rimuovi progetto"
msgid "Request Access"
msgstr "Richiedi accesso"
msgid "Revert this commit"
msgstr "Ripristina questo commit"
msgid "Revert this merge request"
msgstr "Ripristina questa richiesta di merge"
msgid "Save pipeline schedule"
msgstr "Salva pianificazione pipeline"
msgid "Schedule a new pipeline"
msgstr "Pianifica una nuova Pipeline"
msgid "Scheduling Pipelines"
msgstr "Pianificazione pipelines"
msgid "Search branches and tags"
msgstr "Ricerca branches e tags"
msgid "Select Archive Format"
msgstr "Seleziona formato d'archivio"
msgid "Select a timezone"
msgstr "Seleziona una timezone"
msgid "Select target branch"
msgstr "Seleziona una branch di destinazione"
msgid "Set a password on your account to pull or push via %{protocol}"
msgstr ""
"Imposta una password sul tuo account per eseguire pull o push tramite "
"%{protocol}"
msgid "Set up CI"
msgstr "Configura CI"
msgid "Set up Koding"
msgstr "Configura Koding"
msgid "Set up auto deploy"
msgstr "Configura il rilascio automatico"
msgid "SetPasswordToCloneLink|set a password"
msgstr "imposta una password"
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Visualizza %d evento"
msgstr[1] "Visualizza %d eventi"
msgid "Source code"
msgstr "Codice Sorgente"
msgid "StarProject|Star"
msgstr "Star"
msgid "Start a %{new_merge_request} with these changes"
msgstr "inizia una %{new_merge_request} con queste modifiche"
msgid "Switch branch/tag"
msgstr "Cambia branch/tag"
msgid "Tag"
msgid_plural "Tags"
msgstr[0] "Tag"
msgstr[1] "Tags"
msgid "Tags"
msgstr "Tags"
msgid "Target Branch"
msgstr "Branch di destinazione"
msgid ""
"The coding stage shows the time from the first commit to creating the merge "
"request. The data will automatically be added here once you create your "
"first merge request."
msgstr ""
"Lo stadio di programmazione mostra il tempo trascorso dal primo commit alla "
"creazione di una richiesta di merge (MR). I dati saranno aggiunti una volta "
"che avrai creato la prima richiesta di merge."
msgid "The collection of events added to the data gathered for that stage."
msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio."
msgid "The fork relationship has been removed."
msgstr "La relazione del fork è stata rimossa"
msgid ""
"The issue stage shows the time it takes from creating an issue to assigning "
"the issue to a milestone, or add the issue to a list on your Issue Board. "
"Begin creating issues to see data for this stage."
msgstr ""
"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue "
"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea "
"un issue per vedere questo stadio."
msgid "The phase of the development lifecycle."
msgstr "Il ciclo vitale della fase di sviluppo."
msgid ""
"The pipelines schedule runs pipelines in the future, repeatedly, for "
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
"Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per "
"specifici tag o branch ed ereditano restrizioni di progetto basate "
"sull'utente ad esse associato."
msgid ""
"The planning stage shows the time from the previous step to pushing your "
"first commit. This time will be added automatically once you push your first "
"commit."
msgstr ""
"Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al "
"suo step precedente. Questo periodo sarà disponibile automaticamente nel "
"momento in cui farai il primo commit."
msgid ""
"The production stage shows the total time it takes between creating an issue "
"and deploying the code to production. The data will be automatically added "
"once you have completed the full idea to production cycle."
msgstr ""
"Lo stadio di produzione mostra il tempo totale che trascorre tra la "
"creazione di un issue il suo rilascio (inteso come codice) in produzione. "
"Questo dato sarà disponibile automaticamente nel momento in cui avrai "
"completato l'intero processo ideale del ciclo di produzione"
msgid "The project can be accessed by any logged in user."
msgstr "Qualunque utente autenticato può accedere a questo progetto."
msgid "The project can be accessed without any authentication."
msgstr ""
"Chiunque può accedere a questo progetto (senza alcuna autenticazione)."
msgid "The repository for this project does not exist."
msgstr "La repository di questo progetto non esiste."
msgid ""
"The review stage shows the time from creating the merge request to merging "
"it. The data will automatically be added after you merge your first merge "
"request."
msgstr ""
"Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo "
"svolgimento effettivo. Questo dato sarà disponibile appena avrai completato "
"una MR (Merger Request)"
msgid ""
"The staging stage shows the time between merging the MR and deploying code "
"to the production environment. The data will be automatically added once you "
"deploy to production for the first time."
msgstr ""
"Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta "
"di Merge) completata al suo rilascio in ambiente di produzione. Questa "
"informazione sarà disponibile dal tuo primo rilascio in produzione"
msgid ""
"The testing stage shows the time GitLab CI takes to run every pipeline for "
"the related merge request. The data will automatically be added after your "
"first pipeline finishes running."
msgstr ""
"Lo stadio di test mostra il tempo che ogni Pipeline impiega per essere "
"eseguita in ogni Richiesta di Merge correlata. L'informazione sarà "
"disponibile automaticamente quando la tua prima Pipeline avrà finito d'esser "
"eseguita."
msgid "The time taken by each data entry gathered by that stage."
msgstr ""
"Il tempo aggregato relativo eventi/data entry raccolto in quello stadio."
msgid ""
"The value lying at the midpoint of a series of observed values. E.g., "
"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
" 6."
msgstr ""
"Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il "
"mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6."
msgid ""
"This means you can not push code until you create an empty repository or "
"import existing one."
msgstr ""
"Questo significa che non è possibile effettuare push di codice fino a che "
"non crei una repository vuota o ne importi una esistente"
msgid "Time before an issue gets scheduled"
msgstr "Il tempo che impiega un issue per esser pianificato"
msgid "Time before an issue starts implementation"
msgstr "Il tempo che impiega un issue per esser implementato"
msgid "Time between merge request creation and merge/close"
msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close"
msgid "Time until first merge request"
msgstr "Il tempo fino alla prima richiesta di merge"
msgid "Timeago|%s days ago"
msgstr "%s giorni fa"
msgid "Timeago|%s days remaining"
msgstr "%s giorni rimanenti"
msgid "Timeago|%s hours remaining"
msgstr "%s ore rimanenti"
msgid "Timeago|%s minutes ago"
msgstr "%s minuti fa"
msgid "Timeago|%s minutes remaining"
msgstr "%s minuti rimanenti"
msgid "Timeago|%s months ago"
msgstr "%s minuti fa"
msgid "Timeago|%s months remaining"
msgstr "%s mesi rimanenti"
msgid "Timeago|%s seconds remaining"
msgstr "%s secondi rimanenti"
msgid "Timeago|%s weeks ago"
msgstr "%s settimane fa"
msgid "Timeago|%s weeks remaining"
msgstr "%s settimane rimanenti"
msgid "Timeago|%s years ago"
msgstr "%s anni fa"
msgid "Timeago|%s years remaining"
msgstr "%s anni rimanenti"
msgid "Timeago|1 day remaining"
msgstr "1 giorno rimanente"
msgid "Timeago|1 hour remaining"
msgstr "1 ora rimanente"
msgid "Timeago|1 minute remaining"
msgstr "1 minuto rimanente"
msgid "Timeago|1 month remaining"
msgstr "1 mese rimanente"
msgid "Timeago|1 week remaining"
msgstr "1 settimana rimanente"
msgid "Timeago|1 year remaining"
msgstr "1 anno rimanente"
msgid "Timeago|Past due"
msgstr "Entro"
msgid "Timeago|a day ago"
msgstr "un giorno fa"
msgid "Timeago|a month ago"
msgstr "un mese fa"
msgid "Timeago|a week ago"
msgstr "una settimana fa"
msgid "Timeago|a while"
msgstr "poco fa"
msgid "Timeago|a year ago"
msgstr "un anno fa"
msgid "Timeago|about %s hours ago"
msgstr "circa %s ore fa"
msgid "Timeago|about a minute ago"
msgstr "circa un minuto fa"
msgid "Timeago|about an hour ago"
msgstr "circa un ora fa"
msgid "Timeago|in %s days"
msgstr "in %s giorni"
msgid "Timeago|in %s hours"
msgstr "in %s ore"
msgid "Timeago|in %s minutes"
msgstr "in %s minuti"
msgid "Timeago|in %s months"
msgstr "in %s mesi"
msgid "Timeago|in %s seconds"
msgstr "in %s secondi"
msgid "Timeago|in %s weeks"
msgstr "in %s settimane"
msgid "Timeago|in %s years"
msgstr "in %s anni"
msgid "Timeago|in 1 day"
msgstr "in 1 giorno"
msgid "Timeago|in 1 hour"
msgstr "in 1 ora"
msgid "Timeago|in 1 minute"
msgstr "in 1 minuto"
msgid "Timeago|in 1 month"
msgstr "in 1 mese"
msgid "Timeago|in 1 week"
msgstr "in 1 settimana"
msgid "Timeago|in 1 year"
msgstr "in 1 anno"
msgid "Timeago|less than a minute ago"
msgstr "meno di un minuto fa"
msgid "Time|hr"
msgid_plural "Time|hrs"
msgstr[0] "hr"
msgstr[1] "hr"
msgid "Time|min"
msgid_plural "Time|mins"
msgstr[0] "min"
msgstr[1] "mins"
msgid "Time|s"
msgstr "s"
msgid "Total Time"
msgstr "Tempo Totale"
msgid "Total test time for all commits/merges"
msgstr "Tempo totale di test per tutti i commits/merges"
msgid "Unstar"
msgstr "Unstar"
msgid "Upload New File"
msgstr "Carica un nuovo file"
msgid "Upload file"
msgstr "Carica file"
msgid "Use your global notification setting"
msgstr "Usa le tue impostazioni globali "
msgid "VisibilityLevel|Internal"
msgstr "Interno"
msgid "VisibilityLevel|Private"
msgstr "Privato"
msgid "VisibilityLevel|Public"
msgstr "Pubblico"
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie."
msgid "We don't have enough data to show this stage."
msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
msgid "Withdraw Access Request"
msgstr "Ritira richiesta d'accesso"
msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr ""
"Stai per rimuovere %{project_name_with_namespace}.\n"
"I progetti rimossi NON POSSONO essere ripristinati\n"
"Sei assolutamente sicuro?"
msgid ""
"You are going to remove the fork relationship to source project "
"%{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr ""
"Stai per rimuovere la relazione con il progetto sorgente "
"%{forked_from_project}. Sei ASSOLUTAMENTE sicuro?"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
"Are you ABSOLUTELY sure?"
msgstr ""
"Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei "
"ASSOLUTAMENTE sicuro?"
msgid "You can only add files when you are on a branch"
msgstr "Puoi aggiungere files solo quando sei in una branch"
msgid "You have reached your project limit"
msgstr "Hai raggiunto il tuo limite di progetto"
msgid "You must sign in to star a project"
msgstr "Devi accedere per porre una star al progetto"
msgid "You need permission."
msgstr "Necessiti del permesso."
msgid "You will not get any notifications via email"
msgstr "Non riceverai alcuna notifica via email"
msgid "You will only receive notifications for the events you choose"
msgstr "Riceverai notifiche solo per gli eventi che hai scelto"
msgid ""
"You will only receive notifications for threads you have participated in"
msgstr "Riceverai notifiche solo per i threads a cui hai partecipato"
msgid "You will receive notifications for any activity"
msgstr "Riceverai notifiche per ogni attività"
msgid ""
"You will receive notifications only for comments in which you were "
"@mentioned"
msgstr "Riceverai notifiche solo per i commenti ai quale sei stato menzionato"
msgid ""
"You won't be able to pull or push project code via %{protocol} until you "
"%{set_password_link} on your account"
msgstr ""
"Non sarai in grado di eseguire pull o push di codice tramite %{protocol} "
"fino a che %{set_password_link} nel tuo account."
msgid ""
"You won't be able to pull or push project code via SSH until you "
"%{add_ssh_key_link} to your profile"
msgstr ""
"Non sarai in grado di effettuare push o pull tramite SSH fino a che "
"%{add_ssh_key_link} al tuo profilo"
msgid "Your name"
msgstr "Il tuo nome"
msgid "day"
msgid_plural "days"
msgstr[0] "giorno"
msgstr[1] "giorni"
msgid "new merge request"
msgstr "Nuova richiesta di merge"
msgid "notification emails"
msgstr "Notifiche via email"
msgid "parent"
msgid_plural "parents"
msgstr[0] "parent"
msgstr[1] "parents"
...@@ -135,7 +135,7 @@ feature 'Group', feature: true do ...@@ -135,7 +135,7 @@ feature 'Group', feature: true do
expect(page).not_to have_content('secret-group') expect(page).not_to have_content('secret-group')
end end
describe 'group edit' do describe 'group edit', js: true do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:path) { edit_group_path(group) } let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' } let(:new_name) { 'new-name' }
...@@ -157,8 +157,8 @@ feature 'Group', feature: true do ...@@ -157,8 +157,8 @@ feature 'Group', feature: true do
end end
it 'removes group' do it 'removes group' do
click_link 'Remove group' expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
expect(group.members.all.count).to be_zero
expect(page).to have_content "scheduled for deletion" expect(page).to have_content "scheduled for deletion"
end end
end end
...@@ -212,4 +212,10 @@ feature 'Group', feature: true do ...@@ -212,4 +212,10 @@ feature 'Group', feature: true do
expect(page).to have_content(nested_group.name) expect(page).to have_content(nested_group.name)
end end
end end
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm'
end
end end
...@@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do ...@@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do
# the original method, resulting in infinite recurison when called. # the original method, resulting in infinite recurison when called.
# This is likely a bug with helper modules included into dynamically generated view classes. # This is likely a bug with helper modules included into dynamically generated view classes.
# To work around this, we have to hold on to and call to the original implementation manually. # To work around this, we have to hold on to and call to the original implementation manually.
original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options) original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args| allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
options = original_issue_dropdown_options.bind(original.receiver).call(*args) options = original_issue_dropdown_options.bind(original.receiver).call(*args)
options[:data][:per_page] = 2 options[:data][:per_page] = 2
......
...@@ -41,13 +41,10 @@ feature 'issue move to another project' do ...@@ -41,13 +41,10 @@ feature 'issue move to another project' do
find('#issuable-move', visible: false).set(new_project.id) find('#issuable-move', visible: false).set(new_project.id)
click_button('Save changes') click_button('Save changes')
wait_for_requests
expect(current_url).to include project_path(new_project)
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
expect(page).to have_content(issue.title) expect(page).to have_content(issue.title)
expect(page.current_path).to include project_path(new_project)
end end
scenario 'searching project dropdown', js: true do scenario 'searching project dropdown', js: true do
......
...@@ -49,12 +49,12 @@ describe LabelsFinder do ...@@ -49,12 +49,12 @@ describe LabelsFinder do
end end
context 'filtering by group_id' do context 'filtering by group_id' do
it 'returns labels available for any project within the group' do it 'returns labels available for any non-archived project within the group' do
group_1.add_developer(user) group_1.add_developer(user)
project_1.archive!
finder = described_class.new(user, group_id: group_1.id) finder = described_class.new(user, group_id: group_1.id)
expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5] expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5]
end end
end end
......
require 'spec_helper' require 'spec_helper'
require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do describe 'instrument_classes', lib: true do
let(:config) { double(:config) } let(:config) { double(:config) }
let(:unicorn_sampler) { double(:unicorn_sampler) }
let(:influx_sampler) { double(:influx_sampler) }
before do before do
allow(config).to receive(:instrument_method) allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods) allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_method) allow(config).to receive(:instrument_instance_method)
allow(config).to receive(:instrument_instance_methods) allow(config).to receive(:instrument_instance_methods)
allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
allow(unicorn_sampler).to receive(:start)
allow(influx_sampler).to receive(:start)
allow(Gitlab::Application).to receive(:configure)
end end
it 'can autoload and instrument all files' do it 'can autoload and instrument all files' do
require_relative '../../config/initializers/8_metrics'
expect { instrument_classes(config) }.not_to raise_error expect { instrument_classes(config) }.not_to raise_error
end end
end end
...@@ -13,7 +13,7 @@ const metricsGroupsAPIResponse = { ...@@ -13,7 +13,7 @@ const metricsGroupsAPIResponse = {
'queries': [ 'queries': [
{ {
'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20', 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
'label': 'Container memory', 'y_label': 'Memory',
'unit': 'MiB', 'unit': 'MiB',
'result': [ 'result': [
{ {
...@@ -2477,7 +2477,7 @@ export const singleRowMetrics = [ ...@@ -2477,7 +2477,7 @@ export const singleRowMetrics = [
{ {
'title': 'CPU usage', 'title': 'CPU usage',
'weight': 1, 'weight': 1,
'y_label': 'Values', 'y_label': 'Memory',
'queries': [ 'queries': [
{ {
'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
......
...@@ -94,4 +94,15 @@ describe('MonitoringColumn', () => { ...@@ -94,4 +94,15 @@ describe('MonitoringColumn', () => {
done(); done();
}); });
}); });
it('has a title for the y-axis that comes from the backend', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
expect(component.yAxisLabel).toEqual(component.columnData.y_label);
});
}); });
...@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(95) expect(data.size).to eq(95)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("# Contribute to GitLab") expect(data.first[:line]).to eq("# Contribute to GitLab")
expect(data.first[:line]).to be_utf8
end end
end end
...@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1) expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("Ä ü") expect(data.first[:line]).to eq("Ä ü")
expect(data.first[:line]).to be_utf8
end end
end end
...@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1) expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq(" ") expect(data.first[:line]).to eq(" ")
expect(data.first[:line]).to be_utf8
end end
end end
end end
...@@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do
expect(Gitlab::Git::Commit).to receive(:decorate) expect(Gitlab::Git::Commit).to receive(:decorate)
.with(hash_including(attributes)).and_call_original .with(hash_including(attributes)).and_call_original
expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8) expect(branch.dereferenced_target.message).to be_utf8
end end
end end
......
...@@ -180,7 +180,7 @@ EOT ...@@ -180,7 +180,7 @@ EOT
let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) } let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
it 'encodes diff patch to UTF-8' do it 'encodes diff patch to UTF-8' do
expect(diff.diff.encoding).to eq(Encoding::UTF_8) expect(diff.diff).to be_utf8
end end
end end
end end
......
...@@ -27,16 +27,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -27,16 +27,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(repository.root_ref.encoding).to eq(Encoding.find('UTF-8')) expect(repository.root_ref).to be_utf8
end
context 'with gitaly enabled' do
before do
stub_gitaly
end
after do
Gitlab::GitalyClient.clear_stubs!
end end
it 'gets the branch name from GitalyClient' do it 'gets the branch name from GitalyClient' do
...@@ -56,7 +47,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -56,7 +47,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError) expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
end end
end end
end
describe "#rugged" do describe "#rugged" do
context 'with no Git env stored' do context 'with no Git env stored' do
...@@ -129,21 +119,12 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -129,21 +119,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) expect(subject.first).to be_utf8
end end
it { is_expected.to include("master") } it { is_expected.to include("master") }
it { is_expected.not_to include("branch-from-space") } it { is_expected.not_to include("branch-from-space") }
context 'with gitaly enabled' do
before do
stub_gitaly
end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'gets the branch names from GitalyClient' do it 'gets the branch names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
subject subject
...@@ -161,7 +142,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -161,7 +142,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { subject }.to raise_error(Gitlab::Git::CommandError) expect { subject }.to raise_error(Gitlab::Git::CommandError)
end end
end end
end
describe '#tag_names' do describe '#tag_names' do
subject { repository.tag_names } subject { repository.tag_names }
...@@ -173,7 +153,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -173,7 +153,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) expect(subject.first).to be_utf8
end end
describe '#last' do describe '#last' do
...@@ -183,15 +163,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -183,15 +163,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to include("v1.0.0") } it { is_expected.to include("v1.0.0") }
it { is_expected.not_to include("v5.0.0") } it { is_expected.not_to include("v5.0.0") }
context 'with gitaly enabled' do
before do
stub_gitaly
end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'gets the tag names from GitalyClient' do it 'gets the tag names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
subject subject
...@@ -209,7 +180,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -209,7 +180,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { subject }.to raise_error(Gitlab::Git::CommandError) expect { subject }.to raise_error(Gitlab::Git::CommandError)
end end
end end
end
shared_examples 'archive check' do |extenstion| shared_examples 'archive check' do |extenstion|
it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) } it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) }
...@@ -1281,22 +1251,12 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1281,22 +1251,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end end
context 'with gitaly enabled' do
before do
stub_gitaly
end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'returns a Branch with UTF-8 fields' do it 'returns a Branch with UTF-8 fields' do
branches = @repo.local_branches.to_a branches = @repo.local_branches.to_a
expect(branches.size).to be > 0 expect(branches.size).to be > 0
utf_8 = Encoding.find('utf-8')
branches.each do |branch| branches.each do |branch|
expect(branch.name.encoding).to eq(utf_8) expect(branch.name).to be_utf8
expect(branch.target.encoding).to eq(utf_8) unless branch.target.nil? expect(branch.target).to be_utf8 unless branch.target.nil?
end end
end end
...@@ -1318,7 +1278,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1318,7 +1278,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError) expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
end end
end end
end
def create_remote_branch(remote_name, branch_name, source_branch_name) def create_remote_branch(remote_name, branch_name, source_branch_name)
source_branch = @repo.branches.find { |branch| branch.name == source_branch_name } source_branch = @repo.branches.find { |branch| branch.name == source_branch_name }
...@@ -1395,11 +1354,4 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1395,11 +1354,4 @@ describe Gitlab::Git::Repository, seed_helper: true do
sha = Rugged::Commit.create(repo, options) sha = Rugged::Commit.create(repo, options)
repo.lookup(sha) repo.lookup(sha)
end end
def stub_gitaly
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
stub = double(:stub)
allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub)
end
end end
...@@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do ...@@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do
let(:relative_path) { project.path_with_namespace + '.git' } let(:relative_path) { project.path_with_namespace + '.git' }
let(:client) { described_class.new(project.repository) } let(:client) { described_class.new(project.repository) }
before do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end
after do
# When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
# and because GitalyClient shares stubs these will get passed from example to
# example, which will cause an error, so we clean the stubs after each example.
Gitlab::GitalyClient.clear_stubs!
end
describe '#branch_names' do describe '#branch_names' do
it 'sends a find_all_branch_names message' do it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::Ref::Stub)
...@@ -82,4 +71,13 @@ describe Gitlab::GitalyClient::Ref do ...@@ -82,4 +71,13 @@ describe Gitlab::GitalyClient::Ref do
expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
end end
end end
describe '#find_ref_name', seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:client) { described_class.new(repository) }
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
it { should be_utf8 }
it { should eq('refs/heads/master') }
end
end end
...@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access }.with_indifferent_access
end end
it { is_expected.to all(have_attributes(labels: { shard: :default })) } # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
around(:each) do |example| # rubocop:disable RSpec/AroundBlock
times_to_try = ENV['CI'] ? 4 : 1
example.run_with_retry retry: times_to_try
end
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) } it 'provides metrics' do
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) } expect(subject).to all(have_attributes(labels: { shard: :default }))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end end
context 'storage points to directory that has both read and write rights' do context 'storage points to directory that has both read and write rights' do
before do before do
FileUtils.chmod_R(0755, tmp_dir) FileUtils.chmod_R(0755, tmp_dir)
end end
it { is_expected.to all(have_attributes(labels: { shard: :default })) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) } it 'provides metrics' do
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) } expect(subject).to all(have_attributes(labels: { shard: :default }))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end end
end end
end end
......
require 'spec_helper'
describe Gitlab::Metrics::ConnectionRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
around do |example|
Timecop.freeze { example.run }
end
describe '#call' do
let(:status) { 100 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] }
before do
allow(app).to receive(:call).and_return(stack_result)
end
context '@app.call succeeds with 200' do
before do
allow(app).to receive(:call).and_return([200, nil, nil])
end
it 'increments response count with status label' do
expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get'))
subject.call(env)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
subject.call(env)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
subject.call(env)
end
end
context '@app.call throws exception' do
let(:rack_response_count) { double('rack_response_count') }
before do
allow(app).to receive(:call).and_raise(StandardError)
allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
end
it 'increments exceptions count' do
expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
expect { subject.call(env) }.to raise_error(StandardError)
end
it "does't increment response count" do
expect(described_class.rack_response_count).not_to receive(:increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
raise StandardError
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
expect { subject.call(env) }.to raise_error(StandardError)
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Metrics::Sampler do describe Gitlab::Metrics::InfluxSampler do
let(:sampler) { described_class.new(5) } let(:sampler) { described_class.new(5) }
after do after do
...@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do ...@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
end end
describe '#start' do describe '#start' do
it 'gathers a sample at a given interval' do it 'runs once and gathers a sample at a given interval' do
expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)) expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
expect(sampler).to receive(:sample) expect(sampler).to receive(:sample).once
expect(sampler).to receive(:loop).and_yield expect(sampler).to receive(:running).and_return(false, true, false)
sampler.start.join sampler.start.join
end end
......
require 'spec_helper'
describe Gitlab::Metrics::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
let(:unicorn) { double('unicorn') }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
before do
stub_const('Unicorn', unicorn)
stub_const('Raindrops::Linux', raindrops)
allow(raindrops).to receive(:unix_listener_stats).and_return({})
allow(raindrops).to receive(:tcp_listener_stats).and_return({})
end
context 'unicorn listens on unix sockets' do
let(:socket_address) { '/some/sock' }
let(:sockets) { [socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:unix_listener_stats).with(sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'unix', address: socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'tcp', address: tcp_socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
end
describe '#start' do
context 'when enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
it 'creates new thread' do
expect(Thread).to receive(:new)
subject.start
end
end
context 'when disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
it "doesn't create new thread" do
expect(Thread).not_to receive(:new)
subject.start
end
end
end
end
...@@ -1004,7 +1004,11 @@ describe Ci::Build, :models do ...@@ -1004,7 +1004,11 @@ describe Ci::Build, :models do
'fix-1-foo' => 'fix-1-foo', 'fix-1-foo' => 'fix-1-foo',
'a' * 63 => 'a' * 63, 'a' * 63 => 'a' * 63,
'a' * 64 => 'a' * 63, 'a' * 64 => 'a' * 63,
'FOO' => 'foo' 'FOO' => 'foo',
'-' + 'a' * 61 + '-' => 'a' * 61,
'-' + 'a' * 62 + '-' => 'a' * 62,
'-' + 'a' * 63 + '-' => 'a' * 62,
'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug| }.each do |ref, slug|
it "transforms #{ref} to #{slug}" do it "transforms #{ref} to #{slug}" do
build.ref = ref build.ref = ref
......
require 'spec_helper'
describe Sortable do
let(:relation) { Issue.all }
describe '#where' do
it 'orders by id, descending' do
order_node = relation.where(iid: 1).order_values.first
expect(order_node).to be_a(Arel::Nodes::Descending)
expect(order_node.expr.name).to eq(:id)
end
end
describe '#find_by' do
it 'does not order' do
expect(relation).to receive(:unscope).with(:order).and_call_original
relation.find_by(iid: 1)
end
end
end
...@@ -120,7 +120,6 @@ describe Environment, models: true do ...@@ -120,7 +120,6 @@ describe Environment, models: true do
let(:head_commit) { project.commit } let(:head_commit) { project.commit }
let(:commit) { project.commit.parent } let(:commit) { project.commit.parent }
context 'Gitaly find_ref_name feature disabled' do
it 'returns deployment id for the environment' do it 'returns deployment id for the environment' do
expect(environment.first_deployment_for(commit)).to eq deployment1 expect(environment.first_deployment_for(commit)).to eq deployment1
end end
...@@ -128,20 +127,10 @@ describe Environment, models: true do ...@@ -128,20 +127,10 @@ describe Environment, models: true do
it 'return nil when no deployment is found' do it 'return nil when no deployment is found' do
expect(environment.first_deployment_for(head_commit)).to eq nil expect(environment.first_deployment_for(head_commit)).to eq nil
end end
end
# TODO: Uncomment when feature is reenabled it 'returns a UTF-8 ref' do
# context 'Gitaly find_ref_name feature enabled' do expect(environment.first_deployment_for(commit).ref).to be_utf8
# before do end
# allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
# end
#
# it 'calls GitalyClient' do
# expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
#
# environment.first_deployment_for(commit)
# end
# end
end end
describe '#environment_type' do describe '#environment_type' do
......
...@@ -2,29 +2,49 @@ require 'spec_helper' ...@@ -2,29 +2,49 @@ require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do describe ForkedProjectLink, "add link on fork" do
let(:project_from) { create(:project, :repository) } let(:project_from) { create(:project, :repository) }
let(:project_to) { fork_project(project_from, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { user.namespace } let(:namespace) { user.namespace }
before do before do
create(:project_member, :reporter, user: user, project: project_from) project_from.add_reporter(user)
@project_to = fork_project(project_from, user) end
it 'project_from knows its forks' do
_ = project_to
expect(project_from.forks.count).to eq(1)
end end
it "project_to knows it is forked" do it "project_to knows it is forked" do
expect(@project_to.forked?).to be_truthy expect(project_to.forked?).to be_truthy
end end
it "project knows who it is forked from" do it "project knows who it is forked from" do
expect(@project_to.forked_from_project).to eq(project_from) expect(project_to.forked_from_project).to eq(project_from)
end end
end
describe '#forked?' do context 'project_to is pending_delete' do
let(:forked_project_link) { build(:forked_project_link) } before do
let(:project_from) { create(:project, :repository) } project_to.update!(pending_delete: true)
end
it { expect(project_from.forks.count).to eq(0) }
end
context 'project_from is pending_delete' do
before do
project_from.update!(pending_delete: true)
end
it { expect(project_to.forked_from_project).to be_nil }
end
describe '#forked?' do
let(:project_to) { create(:project, forked_project_link: forked_project_link) } let(:project_to) { create(:project, forked_project_link: forked_project_link) }
let(:forked_project_link) { build(:forked_project_link) }
before :each do before do
forked_project_link.forked_from_project = project_from forked_project_link.forked_from_project = project_from
forked_project_link.forked_to_project = project_to forked_project_link.forked_to_project = project_to
forked_project_link.save! forked_project_link.save!
...@@ -40,15 +60,17 @@ describe '#forked?' do ...@@ -40,15 +60,17 @@ describe '#forked?' do
it "project_to.destroy destroys fork_link" do it "project_to.destroy destroys fork_link" do
expect(forked_project_link).to receive(:destroy) expect(forked_project_link).to receive(:destroy)
project_to.destroy project_to.destroy
end end
end end
def fork_project(from_project, user) def fork_project(from_project, user)
service = Projects::ForkService.new(from_project, user)
shell = double('gitlab_shell', fork_repository: true) shell = double('gitlab_shell', fork_repository: true)
service = Projects::ForkService.new(from_project, user)
allow(service).to receive(:gitlab_shell).and_return(shell) allow(service).to receive(:gitlab_shell).and_return(shell)
service.execute service.execute
end
end end
...@@ -105,6 +105,22 @@ describe MergeRequest, models: true do ...@@ -105,6 +105,22 @@ describe MergeRequest, models: true do
end end
end end
describe '#assignee_ids' do
it 'returns an array of the assigned user id' do
subject.assignee_id = 123
expect(subject.assignee_ids).to eq([123])
end
end
describe '#assignee_ids=' do
it 'sets assignee_id to the last id in the array' do
subject.assignee_ids = [123, 456]
expect(subject.assignee_id).to eq(456)
end
end
describe '#assignee_or_author?' do describe '#assignee_or_author?' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
require 'spec_helper'
describe GlobalPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { GlobalPolicy.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
it { is_expected.to be_allowed(:read_users_list) }
end
context "for an anonymous user" do
let(:current_user) { nil }
context "when the public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it { is_expected.not_to be_allowed(:read_users_list) }
end
context "when the public level is not restricted" do
before do
stub_application_setting(restricted_visibility_levels: [])
end
it { is_expected.to be_allowed(:read_users_list) }
end
end
end
end
...@@ -13,9 +13,40 @@ describe API::Users do ...@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do describe 'GET /users' do
context "when unauthenticated" do context "when unauthenticated" do
it "returns authentication error" do it "returns authorization error when the `username` parameter is not passed" do
get api("/users") get api("/users")
expect(response).to have_http_status(401)
expect(response).to have_http_status(403)
end
it "returns the user when a valid `username` parameter is passed" do
user = create(:user)
get api("/users"), username: user.username
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(user.id)
expect(json_response[0]['username']).to eq(user.username)
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
user = create(:user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
get api("/users"), username: user.username
expect(response).to have_http_status(403)
end
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end end
end end
...@@ -138,6 +169,7 @@ describe API::Users do ...@@ -138,6 +169,7 @@ describe API::Users do
describe "GET /users/:id" do describe "GET /users/:id" do
it "returns a user by id" do it "returns a user by id" do
get api("/users/#{user.id}", user) get api("/users/#{user.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username) expect(json_response['username']).to eq(user.username)
end end
...@@ -148,9 +180,22 @@ describe API::Users do ...@@ -148,9 +180,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil expect(json_response['is_admin']).to be_nil
end end
it "returns a 401 if unauthenticated" do context 'for an anonymous user' do
get api("/users/9998") it "returns a user by id" do
expect(response).to have_http_status(401) get api("/users/#{user.id}")
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
it "returns a 404 if the target user is present but inaccessible" do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
get api("/users/#{user.id}")
expect(response).to have_http_status(404)
end
end end
it "returns a 404 error if user id not found" do it "returns a 404 error if user id not found" do
......
...@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do ...@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do
expect(project.repository.branch_names).to include('master') expect(project.repository.branch_names).to include('master')
end end
it 'keeps protected branches' do
create(:protected_branch, project: project, name: 'improve/awesome')
service.execute
expect(project.repository.branch_names).to include('improve/awesome')
end
context 'user without rights' do context 'user without rights' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe MergeRequests::MergeService, services: true do describe MergeRequests::MergeService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) } let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
let(:project) { merge_request.project } let(:project) { merge_request.project }
before do before do
...@@ -133,14 +133,46 @@ describe MergeRequests::MergeService, services: true do ...@@ -133,14 +133,46 @@ describe MergeRequests::MergeService, services: true do
it { expect(todo).to be_done } it { expect(todo).to be_done }
end end
context 'remove source branch by author' do context 'source branch removal' do
context 'when the source branch is protected' do
let(:service) do
MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
end
before do
create(:protected_branch, project: project, name: merge_request.source_branch)
end
it 'does not delete the source branch' do
expect(DeleteBranchService).not_to receive(:new)
service.execute(merge_request)
end
end
context 'when the source branch is the default branch' do
let(:service) do
MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
end
before do
allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
end
it 'does not delete the source branch' do
expect(DeleteBranchService).not_to receive(:new)
service.execute(merge_request)
end
end
context 'when the source branch can be removed' do
context 'when MR author set the source branch to be removed' do
let(:service) do let(:service) do
merge_request.merge_params['force_remove_source_branch'] = '1' merge_request.merge_params['force_remove_source_branch'] = '1'
merge_request.save! merge_request.save!
MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
end end
it 'removes the source branch' do it 'removes the source branch using the author user' do
expect(DeleteBranchService).to receive(:new) expect(DeleteBranchService).to receive(:new)
.with(merge_request.source_project, merge_request.author) .with(merge_request.source_project, merge_request.author)
.and_call_original .and_call_original
...@@ -148,6 +180,21 @@ describe MergeRequests::MergeService, services: true do ...@@ -148,6 +180,21 @@ describe MergeRequests::MergeService, services: true do
end end
end end
context 'when MR merger set the source branch to be removed' do
let(:service) do
MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1')
end
it 'removes the source branch using the current user' do
expect(DeleteBranchService).to receive(:new)
.with(merge_request.source_project, user)
.and_call_original
service.execute(merge_request)
end
end
end
end
context "error handling" do context "error handling" do
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
......
...@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do ...@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do
let(:content) { "/assign @#{developer.username}" } let(:content) { "/assign @#{developer.username}" }
context 'Issue' do context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue) _, updates = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [developer.id]) expect(updates[:assignee_ids]).to match_array([developer.id])
end end
end end
context 'Merge Request' do context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id) expect(updates).to eq(assignee_ids: [developer.id])
end end
end end
end end
...@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do ...@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Issue' do context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue) _, updates = service.execute(content, issue)
expect(updates[:assignee_ids]).to match_array([developer.id]) expect(updates[:assignee_ids]).to match_array([developer.id])
...@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do ...@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Merge Request' do context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id) expect(updates).to eq(assignee_ids: [developer.id])
end end
end end
end end
...@@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do ...@@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Merge Request' do context 'Merge Request' do
it 'populates assignee_id: nil if content contains /unassign' do it 'populates assignee_ids: [] if content contains /unassign' do
merge_request.update(assignee_id: developer.id) merge_request.update(assignee_ids: [developer.id])
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: nil) expect(updates).to eq(assignee_ids: [])
end
end
end
context 'reassign command' do
let(:content) { '/reassign' }
context 'Issue' do
it 'reassigns user if content contains /reassign @user' do
user = create(:user)
issue.update(assignee_ids: [developer.id])
_, updates = service.execute("/reassign @#{user.username}", issue)
expect(updates).to eq(assignee_ids: [user.id])
end end
end end
end end
......
RSpec::Matchers.define :be_utf8 do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
end
description do
"be a String with encoding UTF-8"
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