...@@ -74,17 +74,12 @@ ...@@ -74,17 +74,12 @@
// Middle dot divider between each element in a list of items. // Middle dot divider between each element in a list of items.
.middle-dot-divider { .middle-dot-divider {
&::after { @include middle-dot-divider;
content: '\00B7'; // Middle Dot }
padding: 0 6px;
font-weight: $gl-font-weight-bold;
}
&:last-child { .middle-dot-divider-sm {
&::after { @include media-breakpoint-up(sm) {
content: ''; @include middle-dot-divider;
padding: 0;
}
} }
} }
...@@ -202,10 +197,6 @@ ...@@ -202,10 +197,6 @@
} }
.user-profile { .user-profile {
.cover-controls a {
margin-left: 5px;
}
.profile-header { .profile-header {
margin: 0 $gl-padding; margin: 0 $gl-padding;
... ...
......
...@@ -12,11 +12,7 @@ class Projects::Tags::ReleasesController < Projects::ApplicationController ...@@ -12,11 +12,7 @@ class Projects::Tags::ReleasesController < Projects::ApplicationController
end end
def update def update
if release_params[:description].present? release.update(release_params) if release.persisted? || release_params[:description].present?
release.update(release_params)
else
release.destroy
end
redirect_to project_tag_path(@project, tag.name) redirect_to project_tag_path(@project, tag.name)
end end
... ...
......
...@@ -66,7 +66,7 @@ module SubmoduleHelper ...@@ -66,7 +66,7 @@ module SubmoduleHelper
project].join('') project].join('')
url_with_dotgit = url_no_dotgit + '.git' url_with_dotgit = url_no_dotgit + '.git'
url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join('')) url_with_dotgit == Gitlab::Shell.url_to_repo([namespace, '/', project].join(''))
end end
def relative_self_url?(url) def relative_self_url?(url)
... ...
......
...@@ -91,6 +91,21 @@ module UsersHelper ...@@ -91,6 +91,21 @@ module UsersHelper
end end
end end
def work_information(user)
return unless user
organization = user.organization
job_title = user.job_title
if organization.present? && job_title.present?
s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization }
elsif job_title.present?
job_title
elsif organization.present?
organization
end
end
private private
def get_profile_tabs def get_profile_tabs
... ...
......
...@@ -61,12 +61,13 @@ module BulkInsertSafe ...@@ -61,12 +61,13 @@ module BulkInsertSafe
super super
end end
# Inserts the given ActiveRecord [items] to the table mapped to this class via [InsertAll]. # Inserts the given ActiveRecord [items] to the table mapped to this class.
# Items will be inserted in batches of a given size, where insertion semantics are # Items will be inserted in batches of a given size, where insertion semantics are
# "atomic across all batches", i.e. either all items will be inserted or none. # "atomic across all batches".
# #
# @param [Boolean] validate Whether validations should run on [items] # @param [Boolean] validate Whether validations should run on [items]
# @param [Integer] batch_size How many items should at most be inserted at once # @param [Integer] batch_size How many items should at most be inserted at once
# @param [Boolean] skip_duplicates Marks duplicates as allowed, and skips inserting them
# @param [Proc] handle_attributes Block that will receive each item attribute hash # @param [Proc] handle_attributes Block that will receive each item attribute hash
# prior to insertion for further processing # prior to insertion for further processing
# #
...@@ -75,26 +76,65 @@ module BulkInsertSafe ...@@ -75,26 +76,65 @@ module BulkInsertSafe
# - [ActiveRecord::RecordInvalid] on entity validation failures # - [ActiveRecord::RecordInvalid] on entity validation failures
# - [ActiveRecord::RecordNotUnique] on duplicate key errors # - [ActiveRecord::RecordNotUnique] on duplicate key errors
# #
# @return true if all items succeeded to be inserted, throws otherwise. # @return true if operation succeeded, throws otherwise.
# #
def bulk_insert!(items, validate: true, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes) def bulk_insert!(items, validate: true, skip_duplicates: false, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes)
return true if items.empty? _bulk_insert_all!(items,
validate: validate,
_bulk_insert_in_batches(items, batch_size, validate, &handle_attributes) on_duplicate: skip_duplicates ? :skip : :raise,
unique_by: nil,
batch_size: batch_size,
&handle_attributes)
end
true # Upserts the given ActiveRecord [items] to the table mapped to this class.
# Items will be inserted or updated in batches of a given size,
# where insertion semantics are "atomic across all batches".
#
# @param [Boolean] validate Whether validations should run on [items]
# @param [Integer] batch_size How many items should at most be inserted at once
# @param [Symbol/Array] unique_by Defines index or columns to use to consider item duplicate
# @param [Proc] handle_attributes Block that will receive each item attribute hash
# prior to insertion for further processing
#
# Unique indexes can be identified by columns or name:
# - unique_by: :isbn
# - unique_by: %i[ author_id name ]
# - unique_by: :index_books_on_isbn
#
# Note that this method will throw on the following occasions:
# - [PrimaryKeySetError] when primary keys are set on entities prior to insertion
# - [ActiveRecord::RecordInvalid] on entity validation failures
# - [ActiveRecord::RecordNotUnique] on duplicate key errors
#
# @return true if operation succeeded, throws otherwise.
#
def bulk_upsert!(items, unique_by:, validate: true, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes)
_bulk_insert_all!(items,
validate: validate,
on_duplicate: :update,
unique_by: unique_by,
batch_size: batch_size,
&handle_attributes)
end end
private private
def _bulk_insert_in_batches(items, batch_size, validate_items, &handle_attributes) def _bulk_insert_all!(items, on_duplicate:, unique_by:, validate:, batch_size:, &handle_attributes)
return true if items.empty?
transaction do transaction do
items.each_slice(batch_size) do |item_batch| items.each_slice(batch_size) do |item_batch|
attributes = _bulk_insert_item_attributes(item_batch, validate_items, &handle_attributes) attributes = _bulk_insert_item_attributes(
item_batch, validate, &handle_attributes)
insert_all!(attributes) ActiveRecord::InsertAll
.new(self, attributes, on_duplicate: on_duplicate, unique_by: unique_by)
.execute
end end
end end
true
end end
def _bulk_insert_item_attributes(items, validate_items) def _bulk_insert_item_attributes(items, validate_items)
... ...
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
# needs any special behavior. # needs any special behavior.
module HasRepository module HasRepository
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Gitlab::ShellAdapter
include AfterCommitQueue include AfterCommitQueue
include Referable include Referable
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
...@@ -78,7 +77,7 @@ module HasRepository ...@@ -78,7 +77,7 @@ module HasRepository
end end
def url_to_repo def url_to_repo
gitlab_shell.url_to_repo(full_path) Gitlab::Shell.url_to_repo(full_path)
end end
def ssh_url_to_repo def ssh_url_to_repo
... ...
......
...@@ -1460,13 +1460,14 @@ class Project < ApplicationRecord ...@@ -1460,13 +1460,14 @@ class Project < ApplicationRecord
# Forked import is handled asynchronously # Forked import is handled asynchronously
return if forked? && !force return if forked? && !force
if gitlab_shell.create_project_repository(self) repository.create_repository
repository.after_create repository.after_create
true
else true
errors.add(:base, _('Failed to create repository via gitlab-shell')) rescue => err
false Gitlab::ErrorTracking.track_exception(err, project: { id: id, full_path: full_path, disk_path: disk_path })
end errors.add(:base, _('Failed to create repository'))
false
end end
def hook_attrs(backward: true) def hook_attrs(backward: true)
... ...
......
# frozen_string_literal: true # frozen_string_literal: true
class ProjectWiki class ProjectWiki
include Gitlab::ShellAdapter
include Storage::LegacyProjectWiki include Storage::LegacyProjectWiki
include Gitlab::Utils::StrongMemoize
MARKUPS = { MARKUPS = {
'Markdown' => :markdown, 'Markdown' => :markdown,
...@@ -47,7 +47,7 @@ class ProjectWiki ...@@ -47,7 +47,7 @@ class ProjectWiki
end end
def url_to_repo def url_to_repo
gitlab_shell.url_to_repo(full_path) Gitlab::Shell.url_to_repo(full_path)
end end
def ssh_url_to_repo def ssh_url_to_repo
...@@ -64,14 +64,15 @@ class ProjectWiki ...@@ -64,14 +64,15 @@ class ProjectWiki
# Returns the Gitlab::Git::Wiki object. # Returns the Gitlab::Git::Wiki object.
def wiki def wiki
@wiki ||= begin strong_memoize(:wiki) do
gl_repository = Gitlab::GlRepository::WIKI.identifier_for_container(project) repository.create_if_not_exists
raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path) raise CouldNotCreateWikiError unless repository_exists?
create_repo!(raw_repository) unless raw_repository.exists? Gitlab::Git::Wiki.new(repository.raw)
Gitlab::Git::Wiki.new(raw_repository)
end end
rescue => err
Gitlab::ErrorTracking.track_exception(err, project_wiki: { project_id: project.id, full_path: full_path, disk_path: disk_path })
raise CouldNotCreateWikiError
end end
def repository_exists? def repository_exists?
...@@ -193,14 +194,6 @@ class ProjectWiki ...@@ -193,14 +194,6 @@ class ProjectWiki
private private
def create_repo!(raw_repository)
gitlab_shell.create_wiki_repository(project)
raise CouldNotCreateWikiError unless raw_repository.exists?
repository.after_create
end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title) commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(@user) git_user = Gitlab::Git::User.from_gitlab(@user)
... ...
......
...@@ -24,7 +24,7 @@ class Release < ApplicationRecord ...@@ -24,7 +24,7 @@ class Release < ApplicationRecord
accepts_nested_attributes_for :links, allow_destroy: true accepts_nested_attributes_for :links, allow_destroy: true
validates :description, :project, :tag, presence: true validates :project, :tag, presence: true
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
scope :sorted, -> { order(released_at: :desc) } scope :sorted, -> { order(released_at: :desc) }
... ...
......
...@@ -22,7 +22,7 @@ module Ci ...@@ -22,7 +22,7 @@ module Ci
begin begin
retry_optimistic_lock(ref) do retry_optimistic_lock(ref) do
next false if ref.persisted? && next false if ref.persisted? &&
(ref.last_updated_by_pipeline_id || 0) >= pipeline.id (ref.last_updated_by_pipeline_id || 0) > pipeline.id
ref.update(status: next_status(ref.status, pipeline.status), ref.update(status: next_status(ref.status, pipeline.status),
last_updated_by_pipeline: pipeline) last_updated_by_pipeline: pipeline)
... ...
......
...@@ -52,11 +52,14 @@ module Projects ...@@ -52,11 +52,14 @@ module Projects
checksum = repository.checksum checksum = repository.checksum
# Initialize a git repository on the target path # Initialize a git repository on the target path
gitlab_shell.create_repository(new_storage_key, raw_repository.relative_path, full_path) new_repository = Gitlab::Git::Repository.new(
new_repository = Gitlab::Git::Repository.new(new_storage_key, new_storage_key,
raw_repository.relative_path, raw_repository.relative_path,
raw_repository.gl_repository, raw_repository.gl_repository,
full_path) full_path
)
new_repository.create_repository
new_repository.replicate(raw_repository) new_repository.replicate(raw_repository)
new_checksum = new_repository.checksum new_checksum = new_repository.checksum
... ...
......
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
%p %p
GitLab Shell GitLab Shell
%span.float-right %span.float-right
= Gitlab::Shell.new.version = Gitlab::Shell.version
%p %p
GitLab Workhorse GitLab Workhorse
%span.float-right %span.float-right
... ...
......
- page_title "UI Development Kit", "Help" - page_title "UI Development Kit", "Help"
- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare." - lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare."
- link_classes = "flex-grow-1 mx-1 "
.gitlab-ui-dev-kit .gitlab-ui-dev-kit
%h1 GitLab UI development kit %h1 GitLab UI development kit
...@@ -64,7 +65,12 @@ ...@@ -64,7 +65,12 @@
Cover block for profile page with avatar, name and description Cover block for profile page with avatar, name and description
%code .cover-block %code .cover-block
.example .example
.cover-block .cover-block.user-cover-block
= render layout: 'users/cover_controls' do
= link_to '#', class: link_classes + 'btn btn-default' do
= icon('pencil')
= link_to '#', class: link_classes + 'btn btn-default' do
= icon('rss')
.avatar-holder .avatar-holder
= image_tag avatar_icon_for_email('admin@example.com', 90), class: "avatar s90", alt: '' = image_tag avatar_icon_for_email('admin@example.com', 90), class: "avatar s90", alt: ''
.cover-title .cover-title
...@@ -73,13 +79,6 @@ ...@@ -73,13 +79,6 @@
.cover-desc.cgray .cover-desc.cgray
= lorem = lorem
.cover-controls
= link_to '#', class: 'btn btn-default' do
= icon('pencil')
&nbsp;
= link_to '#', class: 'btn btn-default' do
= icon('rss')
%h2#lists Lists %h2#lists Lists
.lead .lead
... ...
......
...@@ -90,7 +90,6 @@ ...@@ -90,7 +90,6 @@
.row .row
= render 'profiles/name', form: f, user: @user = render 'profiles/name', form: f, user: @user
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' } = f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { prompt: _('Select your role') }, required: true, class: 'input-md'
= render_if_exists 'profiles/email_settings', form: f = render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username") = f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
...@@ -101,6 +100,7 @@ ...@@ -101,6 +100,7 @@
= f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) } = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else - else
= f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country") = f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country")
= f.text_field :job_title, class: 'input-md'
= f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for") = f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for")
= f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters") = f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr %hr
... ...
......
.cover-controls.d-flex.px-2.pb-4.d-sm-block.p-sm-0
= yield
%p %p.mb-1.mb-sm-2.mt-2.mt-sm-3
%span.middle-dot-divider %span.middle-dot-divider
@#{@user.username} @#{@user.username}
- if can?(current_user, :read_user_profile, @user) - if can?(current_user, :read_user_profile, @user)
... ...
......
...@@ -4,30 +4,31 @@ ...@@ -4,30 +4,31 @@
- page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name - page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name
- page_description @user.bio - page_description @user.bio
- header_title @user.name, user_path(@user) - header_title @user.name, user_path(@user)
- link_classes = "flex-grow-1 mx-1 "
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile .user-profile
.cover-block.user-cover-block{ class: [('border-bottom' if profile_tabs.empty?)] } .cover-block.user-cover-block{ class: [('border-bottom' if profile_tabs.empty?)] }
.cover-controls = render layout: 'users/cover_controls' do
- if @user == current_user - if @user == current_user
= link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do = link_to profile_path, class: link_classes + 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
= icon('pencil') = icon('pencil')
- elsif current_user - elsif current_user
- if @user.abuse_report - if @user.abuse_report
%button.btn.btn-danger{ title: s_('UserProfile|Already reported for abuse'), %button{ class: link_classes + 'btn btn-danger mr-1', title: s_('UserProfile|Already reported for abuse'),
data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } } data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } }
= icon('exclamation-circle') = icon('exclamation-circle')
- else - else
= link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn', = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: link_classes + 'btn',
title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('exclamation-circle') = icon('exclamation-circle')
- if can?(current_user, :read_user_profile, @user) - if can?(current_user, :read_user_profile, @user)
= link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do = link_to user_path(@user, rss_url_options), class: link_classes + 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do
= icon('rss') = icon('rss')
- if current_user && current_user.admin? - if current_user && current_user.admin?
= link_to [:admin, @user], class: 'btn btn-default', title: s_('UserProfile|View user in admin area'), = link_to [:admin, @user], class: link_classes + 'btn btn-default', title: s_('UserProfile|View user in admin area'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('users') = icon('users')
...@@ -51,10 +52,18 @@ ...@@ -51,10 +52,18 @@
= emoji_icon(@user.status.emoji) = emoji_icon(@user.status.emoji)
= markdown_field(@user.status, :message) = markdown_field(@user.status, :message)
= render "users/profile_basic_info" = render "users/profile_basic_info"
.cover-desc.cgray .cover-desc.cgray.mb-1.mb-sm-2
- unless @user.public_email.blank? - unless @user.location.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link' = sprite_icon('location', size: 16, css_class: 'vertical-align-sub fgray')
%span.vertical-align-middle
= @user.location
- unless work_information(@user).blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
= sprite_icon('work', size: 16, css_class: 'vertical-align-middle fgray')
%span.vertical-align-middle
= work_information(@user)
.cover-desc.cgray.mb-1.mb-sm-2
- unless @user.skype.blank? - unless @user.skype.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do = link_to "skype:#{@user.skype}", title: "Skype" do
...@@ -64,24 +73,18 @@ ...@@ -64,24 +73,18 @@
= link_to linkedin_url(@user), title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do = link_to linkedin_url(@user), title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('linkedin-square') = icon('linkedin-square')
- unless @user.twitter.blank? - unless @user.twitter.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider-sm
= link_to twitter_url(@user), title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do = link_to twitter_url(@user), title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('twitter-square') = icon('twitter-square')
- unless @user.website_url.blank? - unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow' = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow'
- unless @user.location.blank? - unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= sprite_icon('location', size: 16, css_class: 'vertical-align-sub') = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
= @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
= sprite_icon('work', size: 16, css_class: 'vertical-align-sub')
= @user.organization
- if @user.bio.present? - if @user.bio.present?
.cover-desc.cgray .cover-desc.cgray
%p.profile-user-bio %p.profile-user-bio.font-italic
= @user.bio = @user.bio
- unless profile_tabs.empty? - unless profile_tabs.empty?
... ...
......
---
title: Add CI template to deploy to ECS
merge_request: 26371
author:
type: added
---
title: Optimize Project related count with slack service
merge_request: 26686
author:
type: performance
---
title: Correctly send notification on pipeline retry
merge_request: 26803
author: Jacopo Beschi @jacopo-beschi
type: fixed