Commit 0af7333e authored by Luke Bennett's avatar Luke Bennett

Merge remote-tracking branch 'origin/master' into...

Merge remote-tracking branch 'origin/master' into 39549-label-list-page-redesign-with-draggable-labels
parents bb7f44aa a93e34ac
...@@ -349,7 +349,7 @@ on those issues. Please select someone with relevant experience from the ...@@ -349,7 +349,7 @@ on those issues. Please select someone with relevant experience from the
[GitLab team][team]. If there is nobody mentioned with that expertise look in [GitLab team][team]. If there is nobody mentioned with that expertise look in
the commit history for the affected files to find someone. the commit history for the affected files to find someone.
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/ [described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815 [issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
### Feature proposals ### Feature proposals
......
...@@ -168,6 +168,7 @@ the stable branch are: ...@@ -168,6 +168,7 @@ the stable branch are:
* Fixes for [regressions](#regressions) * Fixes for [regressions](#regressions)
* Fixes for security issues * Fixes for security issues
* Fixes or improvements to automated QA scenarios
* New or updated translations (as long as they do not touch application code) * New or updated translations (as long as they do not touch application code)
During the feature freeze all merge requests that are meant to go into the During the feature freeze all merge requests that are meant to go into the
......
...@@ -58,7 +58,7 @@ class ImporterStatus { ...@@ -58,7 +58,7 @@ class ImporterStatus {
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`); job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
$('table.import-jobs tbody').prepend(job); $('table.import-jobs tbody').prepend(job);
job.addClass('active'); job.addClass('table-active');
const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing'); const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
job.find('.import-actions').html(sprintf( job.find('.import-actions').html(sprintf(
_.escape(__('%{loadingIcon} Started')), { _.escape(__('%{loadingIcon} Started')), {
...@@ -81,7 +81,7 @@ class ImporterStatus { ...@@ -81,7 +81,7 @@ class ImporterStatus {
switch (job.import_status) { switch (job.import_status) {
case 'finished': case 'finished':
jobItem.removeClass('active').addClass('success'); jobItem.removeClass('table-active').addClass('table-success');
statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`); statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
break; break;
case 'scheduled': case 'scheduled':
......
...@@ -56,6 +56,7 @@ export default { ...@@ -56,6 +56,7 @@ export default {
<gl-modal <gl-modal
:id="`modal-peek-${metric}-details`" :id="`modal-peek-${metric}-details`"
:header-title-text="header" :header-title-text="header"
modal-size="lg"
class="performance-bar-modal" class="performance-bar-modal"
> >
<table <table
...@@ -70,7 +71,7 @@ export default { ...@@ -70,7 +71,7 @@ export default {
<td <td
v-for="key in keys" v-for="key in keys"
:key="key" :key="key"
class="break-word" class="break-word all-words"
> >
{{ item[key] }} {{ item[key] }}
</td> </td>
......
...@@ -265,10 +265,10 @@ export default { ...@@ -265,10 +265,10 @@ export default {
/> />
<section <section
v-if="mr.maintainerEditAllowed" v-if="mr.allowCollaboration"
class="mr-info-list mr-links" class="mr-info-list mr-links"
> >
{{ s__("mrWidget|Allows edits from maintainers") }} {{ s__("mrWidget|Allows commits from members who can merge to the target branch") }}
</section> </section>
<mr-widget-related-links <mr-widget-related-links
......
...@@ -83,7 +83,7 @@ export default class MergeRequestStore { ...@@ -83,7 +83,7 @@ export default class MergeRequestStore {
this.canBeMerged = data.can_be_merged || false; this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false; this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing; this.mergeOngoing = data.merge_ongoing;
this.maintainerEditAllowed = data.allow_maintainer_to_push; this.allowCollaboration = data.allow_collaboration;
// Cherry-pick and Revert actions related // Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......
<script> <script>
const buttonVariants = ['danger', 'primary', 'success', 'warning']; const buttonVariants = ['danger', 'primary', 'success', 'warning'];
const sizeVariants = ['sm', 'md', 'lg'];
export default { export default {
name: 'GlModal', name: 'GlModal',
props: { props: {
id: { id: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
modalSize: {
type: String,
required: false,
default: 'md',
validator: value => sizeVariants.includes(value),
},
headerTitleText: { headerTitleText: {
type: String, type: String,
required: false, required: false,
...@@ -27,7 +33,11 @@ export default { ...@@ -27,7 +33,11 @@ export default {
default: '', default: '',
}, },
}, },
computed: {
modalSizeClass() {
return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`;
},
},
methods: { methods: {
emitCancel(event) { emitCancel(event) {
this.$emit('cancel', event); this.$emit('cancel', event);
...@@ -48,6 +58,7 @@ export default { ...@@ -48,6 +58,7 @@ export default {
> >
<div <div
class="modal-dialog" class="modal-dialog"
:class="modalSizeClass"
role="document" role="document"
> >
<div class="modal-content"> <div class="modal-content">
......
...@@ -124,15 +124,18 @@ ...@@ -124,15 +124,18 @@
break; break;
} }
}, },
hideOnSmallScreen(item) {
return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
}, },
}; };
</script> </script>
<template> <template>
<div <div
v-if="showPagination" v-if="showPagination"
class="gl-pagination" class="gl-pagination prepend-top-default"
> >
<ul class="pagination clearfix"> <ul class="pagination justify-content-center">
<li <li
v-for="(item, index) in getItems" v-for="(item, index) in getItems"
:key="index" :key="index"
...@@ -142,12 +145,17 @@ ...@@ -142,12 +145,17 @@
'js-next-button': item.next, 'js-next-button': item.next,
'js-last-button': item.last, 'js-last-button': item.last,
'js-first-button': item.first, 'js-first-button': item.first,
'd-none d-md-block': hideOnSmallScreen(item),
separator: item.separator, separator: item.separator,
active: item.active, active: item.active,
disabled: item.disabled disabled: item.disabled || item.separator
}" }"
class="page-item"
> >
<a @click.prevent="changePage(item.title, item.disabled)"> <a
@click.prevent="changePage(item.title, item.disabled)"
class="page-link"
>
{{ item.title }} {{ item.title }}
</a> </a>
</li> </li>
......
...@@ -24,16 +24,54 @@ html { ...@@ -24,16 +24,54 @@ html {
font-size: 14px; font-size: 14px;
} }
legend {
border-bottom: 1px solid $border-color;
margin-bottom: 20px;
}
button, button,
html [type="button"], html [type="button"],
[type="reset"], [type="reset"],
[type="submit"] { [type="submit"],
[role="button"] {
// Override bootstrap reboot // Override bootstrap reboot
-webkit-appearance: inherit; -webkit-appearance: inherit;
cursor: pointer;
} }
[role="button"] { h1,
cursor: pointer; h2,
h3,
h4,
h5,
h6 {
color: $gl-text-color;
font-weight: 600;
}
h1,
.h1,
h2,
.h2,
h3,
.h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h4,
.h4,
h5,
.h5,
h6,
.h6 {
margin-top: 10px;
margin-bottom: 10px;
}
h5,
.h5 {
font-size: $gl-font-size;
} }
input[type="file"] { input[type="file"] {
...@@ -59,6 +97,10 @@ a { ...@@ -59,6 +97,10 @@ a {
} }
} }
kbd {
display: inline-block;
}
code { code {
padding: 2px 4px; padding: 2px 4px;
color: $red-600; color: $red-600;
...@@ -108,6 +150,16 @@ table { ...@@ -108,6 +150,16 @@ table {
color: $gl-text-color-secondary !important; color: $gl-text-color-secondary !important;
} }
.bg-success,
.bg-primary,
.bg-info,
.bg-danger,
.bg-warning {
.card-header {
color: $white-light;
}
}
// Polyfill deprecated selectors // Polyfill deprecated selectors
.hidden { .hidden {
...@@ -182,8 +234,13 @@ table { ...@@ -182,8 +234,13 @@ table {
} }
.nav-tabs { .nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
.nav-link { .nav-link {
border: 0; border-top: 0;
border-left: 0;
border-right: 0;
} }
.nav-item { .nav-item {
......
...@@ -448,6 +448,10 @@ img.emoji { ...@@ -448,6 +448,10 @@ img.emoji {
.break-word { .break-word {
word-wrap: break-word; word-wrap: break-word;
&.all-words {
word-break: break-word;
}
} }
/** COMMON CLASSES **/ /** COMMON CLASSES **/
......
.gl-pagination { .gl-pagination {
text-align: center; a {
border-top: 1px solid $border-color; color: inherit;
margin: 0; text-decoration: none;
margin-top: 0;
.pagination {
padding: 0;
margin: 20px 0;
a {
cursor: pointer;
}
.separator,
.separator:hover {
a {
cursor: default;
background-color: $gray-light;
padding: $gl-vert-padding;
}
}
}
.gap,
.gap:hover {
background-color: $gray-light;
padding: $gl-vert-padding;
cursor: default;
}
}
.card > .gl-pagination {
margin: 0;
}
/**
* Extra-small screen pagination.
*/
@media (max-width: 320px) {
.gl-pagination {
.first,
.last {
display: none;
}
.page-item {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Small screen pagination
*/
@include media-breakpoint-down(xs) {
.gl-pagination {
.pagination li a {
padding: 6px 10px;
}
.page-item {
display: none;
&.active {
display: inline;
}
}
}
}
/**
* Medium screen pagination
*/
@media (min-width: map-get($grid-breakpoints, xs)) and (max-width: map-get($grid-breakpoints, sm)) {
.gl-pagination {
.page-item {
display: none;
&.active,
&.sibling {
display: inline;
}
}
} }
} }
...@@ -485,6 +485,15 @@ ...@@ -485,6 +485,15 @@
.sidebar-collapsed-user { .sidebar-collapsed-user {
padding-bottom: 0; padding-bottom: 0;
margin-bottom: 10px; margin-bottom: 10px;
.author_link {
padding-left: 0;
.avatar {
position: static;
margin: 0;
}
}
} }
.issuable-header-btn { .issuable-header-btn {
......
...@@ -102,10 +102,6 @@ ...@@ -102,10 +102,6 @@
.form-text.text-muted { .form-text.text-muted {
margin-top: 0; margin-top: 0;
} }
.label-light {
margin-bottom: 0;
}
} }
.settings-list-icon { .settings-list-icon {
......
...@@ -130,12 +130,17 @@ class ApplicationController < ActionController::Base ...@@ -130,12 +130,17 @@ class ApplicationController < ActionController::Base
end end
def access_denied!(message = nil) def access_denied!(message = nil)
# If we display a custom access denied message to the user, we don't want to
# hide existence of the resource, rather tell them they cannot access it using
# the provided message
status = message.present? ? :forbidden : :not_found
respond_to do |format| respond_to do |format|
format.any { head :not_found } format.any { head status }
format.html do format.html do
render "errors/access_denied", render "errors/access_denied",
layout: "errors", layout: "errors",
status: 404, status: status,
locals: { message: message } locals: { message: message }
end end
end end
......
...@@ -3,8 +3,12 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -3,8 +3,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
include MembersPresentation include MembersPresentation
include SortingHelper include SortingHelper
def self.admin_not_required_endpoints
%i[index leave request_access]
end
# Authorize # Authorize
before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access] before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access, skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite, :approve_access_request, :leave, :resend_invite,
......
...@@ -76,12 +76,15 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -76,12 +76,15 @@ class Groups::MilestonesController < Groups::ApplicationController
def milestones def milestones
milestones = MilestonesFinder.new(search_params).execute milestones = MilestonesFinder.new(search_params).execute
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
@sort = params[:sort] || 'due_date_asc' @sort = params[:sort] || 'due_date_asc'
MilestoneArray.sort(milestones + legacy_milestones, @sort) MilestoneArray.sort(milestones + legacy_milestones, @sort)
end end
def legacy_milestones
GroupMilestone.build_collection(group, group_projects, params)
end
def milestone def milestone
@milestone = @milestone =
if params[:title] if params[:title]
......
...@@ -15,7 +15,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -15,7 +15,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
def merge_request_params_attributes def merge_request_params_attributes
[ [
:allow_maintainer_to_push, :allow_collaboration,
:assignee_id, :assignee_id,
:description, :description,
:force_remove_source_branch, :force_remove_source_branch,
......
...@@ -23,8 +23,6 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -23,8 +23,6 @@ class Projects::PipelinesController < Projects::ApplicationController
@finished_count = limited_pipelines_count(project, 'finished') @finished_count = limited_pipelines_count(project, 'finished')
@pipelines_count = limited_pipelines_count(project) @pipelines_count = limited_pipelines_count(project)
Gitlab::Ci::Pipeline::Preloader.preload(@pipelines)
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -34,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -34,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController
pipelines: PipelineSerializer pipelines: PipelineSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
.with_pagination(request, response) .with_pagination(request, response)
.represent(@pipelines, disable_coverage: true), .represent(@pipelines, disable_coverage: true, preload: true),
count: { count: {
all: @pipelines_count, all: @pipelines_count,
running: @running_count, running: @running_count,
......
...@@ -13,6 +13,10 @@ module Users ...@@ -13,6 +13,10 @@ module Users
def index def index
@redirect = redirect_path @redirect = redirect_path
if @term.accepted_by_user?(current_user)
flash.now[:notice] = "You have already accepted the Terms of Service as #{current_user.to_reference}"
end
end end
def accept def accept
......
...@@ -126,8 +126,8 @@ module MergeRequestsHelper ...@@ -126,8 +126,8 @@ module MergeRequestsHelper
link_to(url[merge_request.project, merge_request], data: data_attrs, &block) link_to(url[merge_request.project, merge_request], data: data_attrs, &block)
end end
def allow_maintainer_push_unavailable_reason(merge_request) def allow_collaboration_unavailable_reason(merge_request)
return if merge_request.can_allow_maintainer_to_push?(current_user) return if merge_request.can_allow_collaboration?(current_user)
minimum_visibility = [merge_request.target_project.visibility_level, minimum_visibility = [merge_request.target_project.visibility_level,
merge_request.source_project.visibility_level].min merge_request.source_project.visibility_level].min
......
...@@ -389,11 +389,11 @@ module ProjectsHelper ...@@ -389,11 +389,11 @@ module ProjectsHelper
def project_status_css_class(status) def project_status_css_class(status)
case status case status
when "started" when "started"
"active" "table-active"
when "failed" when "failed"
"danger" "table-danger"
when "finished" when "finished"
"success" "table-success"
end end
end end
...@@ -412,7 +412,10 @@ module ProjectsHelper ...@@ -412,7 +412,10 @@ module ProjectsHelper
exports_path = File.join(Settings.shared['path'], 'tmp/project_exports') exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]") filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")
disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path disk_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
end
filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]") filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
end end
......
...@@ -6,7 +6,7 @@ module WorkhorseHelper ...@@ -6,7 +6,7 @@ module WorkhorseHelper
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob)) headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
headers['Content-Disposition'] = 'inline' headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(blob) headers['Content-Type'] = safe_content_type(blob)
head :ok # 'render nothing: true' messes up the Content-Type render plain: ""
end end
# Send a Git diff through Workhorse # Send a Git diff through Workhorse
......
class ApplicationSetting class ApplicationSetting
class Term < ActiveRecord::Base class Term < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
has_many :term_agreements
validates :terms, presence: true validates :terms, presence: true
...@@ -9,5 +10,10 @@ class ApplicationSetting ...@@ -9,5 +10,10 @@ class ApplicationSetting
def self.latest def self.latest
order(:id).last order(:id).last
end end
def accepted_by_user?(user)
user.accepted_term_id == id ||
term_agreements.accepted.where(user: user).exists?
end
end end
end end
...@@ -31,6 +31,14 @@ module Ci ...@@ -31,6 +31,14 @@ module Ci
end end
end end
def self.fabricate(stage)
stage.statuses.ordered.latest
.sort_by(&:sortable_name).group_by(&:group_name)
.map do |group_name, grouped_statuses|
self.new(stage, name: group_name, jobs: grouped_statuses)
end
end
private private
def commit_statuses def commit_statuses
......
...@@ -16,11 +16,7 @@ module Ci ...@@ -16,11 +16,7 @@ module Ci
end end
def groups def groups
@groups ||= statuses.ordered.latest @groups ||= Ci::Group.fabricate(self)
.sort_by(&:sortable_name).group_by(&:group_name)
.map do |group_name, grouped_statuses|
Ci::Group.new(self, name: group_name, jobs: grouped_statuses)
end
end end
def to_param def to_param
......
...@@ -18,7 +18,7 @@ module Ci ...@@ -18,7 +18,7 @@ module Ci
s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count s&.project&.pipelines&.maximum(:iid) || s&.project&.pipelines&.count
end end
has_many :stages has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
...@@ -254,6 +254,20 @@ module Ci ...@@ -254,6 +254,20 @@ module Ci
stage unless stage.statuses_count.zero? stage unless stage.statuses_count.zero?
end end
##
# TODO We do not completely switch to persisted stages because of
# race conditions with setting statuses gitlab-ce#23257.
#
def ordered_stages
return legacy_stages unless complete?
if Feature.enabled?('ci_pipeline_persisted_stages')
stages
else
legacy_stages
end
end
def legacy_stages def legacy_stages
# TODO, this needs refactoring, see gitlab-ce#26481. # TODO, this needs refactoring, see gitlab-ce#26481.
...@@ -416,7 +430,7 @@ module Ci ...@@ -416,7 +430,7 @@ module Ci
def number_of_warnings def number_of_warnings
BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader| BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader|
Build.where(commit_id: pipeline_ids) ::Ci::Build.where(commit_id: pipeline_ids)
.latest .latest
.failed_but_allowed .failed_but_allowed
.group(:commit_id) .group(:commit_id)
...@@ -508,7 +522,8 @@ module Ci ...@@ -508,7 +522,8 @@ module Ci
def update_status def update_status
retry_optimistic_lock(self) do retry_optimistic_lock(self) do
case latest_builds_status case latest_builds_status.to_s
when 'created' then nil
when 'pending' then enqueue when 'pending' then enqueue
when 'running' then run when 'running' then run
when 'success' then succeed when 'success' then succeed
...@@ -516,6 +531,9 @@ module Ci ...@@ -516,6 +531,9 @@ module Ci
when 'canceled' then cancel when 'canceled' then cancel
when 'skipped' then skip when 'skipped' then skip
when 'manual' then block when 'manual' then block
else
raise HasStatus::UnknownStatusError,
"Unknown status `#{latest_builds_status}`"
end end
end end
end end
......
...@@ -219,10 +219,8 @@ module Ci ...@@ -219,10 +219,8 @@ module Ci
cache_attributes(values) cache_attributes(values)
if persist_cached_data? # We save data without validation, it will always change due to `contacted_at`
self.assign_attributes(values) self.update_columns(values) if persist_cached_data?
self.save if self.changed?
end
end end
def pick_build!(build) def pick_build!(build)
......
...@@ -68,16 +68,44 @@ module Ci ...@@ -68,16 +68,44 @@ module Ci
def update_status def update_status
retry_optimistic_lock(self) do retry_optimistic_lock(self) do
case statuses.latest.status case statuses.latest.status
when 'created' then nil
when 'pending' then enqueue when 'pending' then enqueue
when 'running' then run when 'running' then run
when 'success' then succeed when 'success' then succeed
when 'failed' then drop when 'failed' then drop
when 'canceled' then cancel when 'canceled' then cancel
when 'manual' then block when 'manual' then block
when 'skipped' then skip when 'skipped', nil then skip
else skip else
raise HasStatus::UnknownStatusError,
"Unknown status `#{statuses.latest.status}`"
end end
end end
end end
def groups
@groups ||= Ci::Group.fabricate(self)
end
def has_warnings?
number_of_warnings.positive?
end
def number_of_warnings
BatchLoader.for(id).batch(default_value: 0) do |stage_ids, loader|
::Ci::Build.where(stage_id: stage_ids)
.latest
.failed_but_allowed
.group(:stage_id)
.count
.each { |id, amount| loader.call(id, amount) }
end
end
def detailed_status(current_user)
Gitlab::Ci::Status::Stage::Factory
.new(self, current_user)
.fabricate!
end
end end
end end
...@@ -4,11 +4,14 @@ module Avatarable ...@@ -4,11 +4,14 @@ module Avatarable
included do included do
prepend ShadowMethods prepend ShadowMethods
include ObjectStorage::BackgroundMove include ObjectStorage::BackgroundMove
include Gitlab::Utils::StrongMemoize
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
after_initialize :add_avatar_to_batch
end end
module ShadowMethods module ShadowMethods
...@@ -18,6 +21,17 @@ module Avatarable ...@@ -18,6 +21,17 @@ module Avatarable
avatar_path(only_path: args.fetch(:only_path, true)) || super avatar_path(only_path: args.fetch(:only_path, true)) || super
end end
def retrieve_upload(identifier, paths)
upload = retrieve_upload_from_batch(identifier)
# This fallback is needed when deleting an upload, because we may have
# already been removed from the DB. We have to check an explicit `#nil?`
# because it's a BatchLoader instance.
upload = super if upload.nil?
upload
end
end end
def avatar_type def avatar_type
...@@ -52,4 +66,37 @@ module Avatarable ...@@ -52,4 +66,37 @@ module Avatarable
url_base + avatar.local_url url_base + avatar.local_url
end end
# Path that is persisted in the tracking Upload model. Used to fetch the
# upload from the model.
def upload_paths(identifier)
avatar_mounter.blank_uploader.store_dirs.map { |store, path| File.join(path, identifier) }
end
private
def retrieve_upload_from_batch(identifier)
BatchLoader.for(identifier: identifier, model: self).batch(key: self.class) do |upload_params, loader, args|
model_class = args[:key]
paths = upload_params.flat_map do |params|
params[:model].upload_paths(params[:identifier])
end
Upload.where(uploader: AvatarUploader, path: paths).find_each do |upload|
model = model_class.instantiate('id' => upload.model_id)
loader.call({ model: model, identifier: File.basename(upload.path) }, upload)
end
end
end
def add_avatar_to_batch
return unless avatar_mounter
avatar_mounter.read_identifiers.each { |identifier| retrieve_upload_from_batch(identifier) }
end
def avatar_mounter
strong_memoize(:avatar_mounter) { _mounter(:avatar) }
end
end end
...@@ -11,6 +11,8 @@ module HasStatus ...@@ -11,6 +11,8 @@ module HasStatus
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
UnknownStatusError = Class.new(StandardError)
class_methods do class_methods do
def status_sql def status_sql
scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
......
...@@ -36,4 +36,8 @@ module WithUploads ...@@ -36,4 +36,8 @@ module WithUploads
upload.destroy upload.destroy
end end
end end
def retrieve_upload(_identifier, paths)
uploads.find_by(path: paths)
end
end end
...@@ -1125,21 +1125,21 @@ class MergeRequest < ActiveRecord::Base ...@@ -1125,21 +1125,21 @@ class MergeRequest < ActiveRecord::Base
project.merge_requests.merged.where(author_id: author_id).empty? project.merge_requests.merged.where(author_id: author_id).empty?
end end
def allow_maintainer_to_push def allow_collaboration
maintainer_push_possible? && super collaborative_push_possible? && super
end end
alias_method :allow_maintainer_to_push?, :allow_maintainer_to_push alias_method :allow_collaboration?, :allow_collaboration
def maintainer_push_possible? def collaborative_push_possible?
source_project.present? && for_fork? && source_project.present? && for_fork? &&
target_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && target_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
source_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && source_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
!ProtectedBranch.protected?(source_project, source_branch) !ProtectedBranch.protected?(source_project, source_branch)
end end
def can_allow_maintainer_to_push?(user) def can_allow_collaboration?(user)
maintainer_push_possible? && collaborative_push_possible? &&
Ability.allowed?(user, :push_code, source_project) Ability.allowed?(user, :push_code, source_project)
end end
......
...@@ -435,6 +435,10 @@ class Note < ActiveRecord::Base ...@@ -435,6 +435,10 @@ class Note < ActiveRecord::Base
super.merge(noteable: noteable) super.merge(noteable: noteable)
end end
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
private private
def keep_around_commit def keep_around_commit
......
class PersonalSnippet < Snippet class PersonalSnippet < Snippet
include WithUploads
end end
...@@ -228,6 +228,7 @@ class Project < ActiveRecord::Base ...@@ -228,6 +228,7 @@ class Project < ActiveRecord::Base
has_many :commit_statuses has_many :commit_statuses
has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and # Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in # build traces. Currently there's no efficient way of removing this data in
...@@ -1425,8 +1426,14 @@ class Project < ActiveRecord::Base ...@@ -1425,8 +1426,14 @@ class Project < ActiveRecord::Base
Ci::Runner.from("(#{union.to_sql}) ci_runners") Ci::Runner.from("(#{union.to_sql}) ci_runners")
end end
def active_runners
strong_memoize(:active_runners) do
all_runners.active
end
end
def any_runners?(&block) def any_runners?(&block)
all_runners.active.any?(&block) active_runners.any?(&block)
end end
def valid_runners_token?(token) def valid_runners_token?(token)
...@@ -1968,18 +1975,18 @@ class Project < ActiveRecord::Base ...@@ -1968,18 +1975,18 @@ class Project < ActiveRecord::Base
.limit(1) .limit(1)
.select(1) .select(1)
source_of_merge_requests.opened source_of_merge_requests.opened
.where(allow_maintainer_to_push: true) .where(allow_collaboration: true)
.where('EXISTS (?)', developer_access_exists) .where('EXISTS (?)', developer_access_exists)
end end
def branch_allows_maintainer_push?(user, branch_name) def branch_allows_collaboration?(user, branch_name)
return false unless user return false unless user
cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push" cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push"
memoized_results = strong_memoize(:branch_allows_maintainer_push) do memoized_results = strong_memoize(:branch_allows_collaboration) do
Hash.new do |result, cache_key| Hash.new do |result, cache_key|
result[cache_key] = fetch_branch_allows_maintainer_push?(user, branch_name) result[cache_key] = fetch_branch_allows_collaboration?(user, branch_name)
end end
end end
...@@ -2121,18 +2128,18 @@ class Project < ActiveRecord::Base ...@@ -2121,18 +2128,18 @@ class Project < ActiveRecord::Base
raise ex raise ex
end end
def fetch_branch_allows_maintainer_push?(user, branch_name) def fetch_branch_allows_collaboration?(user, branch_name)
check_access = -> do check_access = -> do
next false if empty_repo? next false if empty_repo?
merge_request = source_of_merge_requests.opened merge_request = source_of_merge_requests.opened
.where(allow_maintainer_to_push: true) .where(allow_collaboration: true)
.find_by(source_branch: branch_name) .find_by(source_branch: branch_name)
merge_request&.can_be_merged_by?(user) merge_request&.can_be_merged_by?(user)
end end
if RequestStore.active? if RequestStore.active?
RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_maintainer_push") do RequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
check_access.call check_access.call
end end
else else
......
...@@ -270,6 +270,16 @@ class Repository ...@@ -270,6 +270,16 @@ class Repository
end end
end end
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
format,
append_sha: append_sha
)
end
def expire_tags_cache def expire_tags_cache
expire_method_caches(%i(tag_names tag_count)) expire_method_caches(%i(tag_names tag_count))
@tags = nil @tags = nil
......
...@@ -2,5 +2,7 @@ class TermAgreement < ActiveRecord::Base ...@@ -2,5 +2,7 @@ class TermAgreement < ActiveRecord::Base
belongs_to :term, class_name: 'ApplicationSetting::Term' belongs_to :term, class_name: 'ApplicationSetting::Term'
belongs_to :user belongs_to :user
scope :accepted, -> { where(accepted: true) }
validates :user, :term, presence: true validates :user, :term, presence: true
end end
...@@ -14,8 +14,8 @@ module Ci ...@@ -14,8 +14,8 @@ module Ci
@subject.triggered_by?(@user) @subject.triggered_by?(@user)
end end
condition(:branch_allows_maintainer_push) do condition(:branch_allows_collaboration) do
@subject.project.branch_allows_maintainer_push?(@user, @subject.ref) @subject.project.branch_allows_collaboration?(@user, @subject.ref)
end end
rule { protected_ref }.policy do rule { protected_ref }.policy do
...@@ -25,7 +25,7 @@ module Ci ...@@ -25,7 +25,7 @@ module Ci
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
rule { can?(:public_access) & branch_allows_maintainer_push }.policy do rule { can?(:public_access) & branch_allows_collaboration }.policy do
enable :update_build enable :update_build
enable :update_commit_status enable :update_commit_status
end end
......
...@@ -4,13 +4,13 @@ module Ci ...@@ -4,13 +4,13 @@ module Ci
condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) } condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) }
condition(:branch_allows_maintainer_push) do condition(:branch_allows_collaboration) do
@subject.project.branch_allows_maintainer_push?(@user, @subject.ref) @subject.project.branch_allows_collaboration?(@user, @subject.ref)
end end
rule { protected_ref }.prevent :update_pipeline rule { protected_ref }.prevent :update_pipeline
rule { can?(:public_access) & branch_allows_maintainer_push }.policy do rule { can?(:public_access) & branch_allows_collaboration }.policy do
enable :update_pipeline enable :update_pipeline
end end
......
...@@ -13,7 +13,7 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -13,7 +13,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :squash expose :squash
expose :target_branch expose :target_branch
expose :target_project_id expose :target_project_id
expose :allow_maintainer_to_push expose :allow_collaboration
expose :should_be_rebased?, as: :should_be_rebased expose :should_be_rebased?, as: :should_be_rebased
expose :ff_only_enabled do |merge_request| expose :ff_only_enabled do |merge_request|
......
class PipelineDetailsEntity < PipelineEntity class PipelineDetailsEntity < PipelineEntity
expose :details do expose :details do
expose :legacy_stages, as: :stages, using: StageEntity expose :ordered_stages, as: :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity expose :manual_actions, using: BuildActionEntity
end end
......
class PipelineSerializer < BaseSerializer class PipelineSerializer < BaseSerializer
include WithPagination include WithPagination
InvalidResourceError = Class.new(StandardError)
entity PipelineDetailsEntity entity PipelineDetailsEntity
def represent(resource, opts = {}) def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation) if resource.is_a?(ActiveRecord::Relation)
resource = resource.preload([ resource = resource.preload([
:stages,
:retryable_builds, :retryable_builds,
:cancelable_statuses, :cancelable_statuses,
:trigger_requests, :trigger_requests,
...@@ -20,10 +17,14 @@ class PipelineSerializer < BaseSerializer ...@@ -20,10 +17,14 @@ class PipelineSerializer < BaseSerializer
end end
if paginated? if paginated?
super(@paginator.paginate(resource), opts) resource = paginator.paginate(resource)
else
super(resource, opts)
end end
if opts.delete(:preload)
resource = Gitlab::Ci::Pipeline::Preloader.preload!(resource)
end
super(resource, opts)
end end
def represent_status(resource) def represent_status(resource)
...@@ -36,7 +37,7 @@ class PipelineSerializer < BaseSerializer ...@@ -36,7 +37,7 @@ class PipelineSerializer < BaseSerializer
def represent_stages(resource) def represent_stages(resource)
return {} unless resource.present? return {} unless resource.present?
data = represent(resource, { only: [{ details: [:stages] }] }) data = represent(resource, { only: [{ details: [:stages] }], preload: true })
data.dig(:details, :stages) || [] data.dig(:details, :stages) || []
end end
end end
module ApplicationSettings module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService class UpdateService < ApplicationSettings::BaseService
attr_reader :params, :application_setting
def execute def execute
update_terms(@params.delete(:terms)) update_terms(@params.delete(:terms))
......
...@@ -2,8 +2,7 @@ module Applications ...@@ -2,8 +2,7 @@ module Applications
class CreateService class CreateService
def initialize(current_user, params) def initialize(current_user, params)
@current_user = current_user @current_user = current_user
@params = params @params = params.except(:ip_address)
@ip_address = @params.delete(:ip_address)
end end
def execute(request = nil) def execute(request = nil)
......
...@@ -38,8 +38,8 @@ module MergeRequests ...@@ -38,8 +38,8 @@ module MergeRequests
def filter_params(merge_request) def filter_params(merge_request)
super super
unless merge_request.can_allow_maintainer_to_push?(current_user) unless merge_request.can_allow_collaboration?(current_user)
params.delete(:allow_maintainer_to_push) params.delete(:allow_collaboration)
end end
end end
......
module TestHooks module TestHooks
class ProjectService < TestHooks::BaseService class ProjectService < TestHooks::BaseService
private attr_writer :project
def project def project
@project ||= hook.project @project ||= hook.project
end end
private
def push_events_data def push_events_data
throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo? throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo?
......
...@@ -33,7 +33,7 @@ module ObjectStorage ...@@ -33,7 +33,7 @@ module ObjectStorage
unless current_upload_satisfies?(paths, model) unless current_upload_satisfies?(paths, model)
# the upload we already have isn't right, find the correct one # the upload we already have isn't right, find the correct one
self.upload = uploads.find_by(model: model, path: paths) self.upload = model&.retrieve_upload(identifier, paths)
end end
super super
...@@ -46,7 +46,7 @@ module ObjectStorage ...@@ -46,7 +46,7 @@ module ObjectStorage
end end
def upload=(upload) def upload=(upload)
return unless upload return if upload.nil?
self.object_store = upload.store self.object_store = upload.store
super super
......
...@@ -116,8 +116,8 @@ ...@@ -116,8 +116,8 @@
.card-body .card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row .form-group.row
= f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-2' = f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-3'
.col-sm-10 .col-sm-9
.dropdown .dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
...@@ -127,7 +127,7 @@ ...@@ -127,7 +127,7 @@
= dropdown_loading = dropdown_loading
.form-group.row .form-group.row
.offset-sm-2.col-sm-10 .offset-sm-3.col-sm-9
= f.submit 'Transfer', class: 'btn btn-primary' = f.submit 'Transfer', class: 'btn btn-primary'
.card.repository-check .card.repository-check
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
%th Projects %th Projects
%th Jobs %th Jobs
%th Tags %th Tags
%th= link_to 'Last contact', admin_runners_path(params.slice(:search).merge(sort: 'contacted_asc')) %th= link_to 'Last contact', admin_runners_path(safe_params.slice(:search).merge(sort: 'contacted_asc'))
%th %th
- @runners.each do |runner| - @runners.each do |runner|
......
.nav-block .nav-block.activities
.controls .controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
......
This diff is collapsed.
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.first.page-item %li.page-item.js-first-button
= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link'
...@@ -4,5 +4,5 @@ ...@@ -4,5 +4,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.page-item.disabled %li.page-item.disabled.d-none.d-md-block
= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.last.page-item %li.page-item.js-last-button
= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote, class: 'page-link'} = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote, class: 'page-link'}
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
- page_url = current_page.last? ? '#' : url - page_url = current_page.last? ? '#' : url
%li.page-item{ class: ('disabled' if current_page.last?) } %li.page-item.js-next-button{ class: ('disabled' if current_page.last?) }
= link_to raw(t 'views.pagination.next'), page_url, rel: 'next', remote: remote, class: 'page-link' = link_to raw(t 'views.pagination.next'), page_url, rel: 'next', remote: remote, class: 'page-link'
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
-# total_pages: total number of pages -# total_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.page-item.js-pagination-page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] } %li.page-item.js-pagination-page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?), ('d-none d-md-block' if !page.current?) ] }
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' } = link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
-# remote: data-remote -# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside -# paginator: the paginator that renders the pagination tags inside
= paginator.render do = paginator.render do
.gl-pagination .gl-pagination.prepend-top-default
%ul.pagination.justify-content-center %ul.pagination.justify-content-center
- unless current_page.first? - unless current_page.first?
= first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages = first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages
......
...@@ -8,5 +8,5 @@ ...@@ -8,5 +8,5 @@
- page_url = current_page.first? ? '#' : url - page_url = current_page.first? ? '#' : url
%li.page-item{ class: ('disabled' if current_page.first?) } %li.page-item.js-previous-button{ class: ('disabled' if current_page.first?) }
= link_to raw(t 'views.pagination.previous'), page_url, rel: 'prev', remote: remote, class: 'page-link' = link_to raw(t 'views.pagination.previous'), page_url, rel: 'prev', remote: remote, class: 'page-link'
.gl-pagination .gl-pagination.prepend-top-default
%ul.pagination.clearfix %ul.pagination.justify-content-center
- if previous_path - if previous_path
%li.page-item.prev %li.page-item.prev
= link_to(t('views.pagination.previous'), previous_path, rel: 'prev', class: 'page-link') = link_to(t('views.pagination.previous'), previous_path, rel: 'prev', class: 'page-link')
......
...@@ -29,16 +29,16 @@ ...@@ -29,16 +29,16 @@
.col-lg-9.js-toggle-container .col-lg-9.js-toggle-container
%ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' } %ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' }
%li{ class: active_when(active_tab == 'blank'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Blank project %span.d-none.d-sm-block Blank project
%span.d-block.d-sm-none Blank %span.d-block.d-sm-none Blank
%li{ class: active_when(active_tab == 'template'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Create from template %span.d-none.d-sm-block Create from template
%span.d-block.d-sm-none Template %span.d-block.d-sm-none Template
%li{ class: active_when(active_tab == 'import'), role: 'presentation' } %li.nav-item{ role: 'presentation' }
%a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' } %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' }
%span.d-none.d-sm-block Import project %span.d-none.d-sm-block Import project
%span.d-block.d-sm-none Import %span.d-block.d-sm-none Import
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.errors-holder .errors-holder
.card-body .card-body
%p %p
Removing the pages will prevent from exposing them to outside world. Removing pages will prevent them from being exposed to the outside world.
.form-actions .form-actions
= link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else - else
......
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
= _('Contribution') = _('Contribution')
.col-sm-10 .col-sm-10
.form-check .form-check
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user), class: 'form-check-input' = form.check_box :allow_collaboration, disabled: !issuable.can_allow_collaboration?(current_user), class: 'form-check-input'
= form.label :allow_maintainer_to_push, class: 'form-check-label' do = form.label :allow_collaboration, class: 'form-check-label' do
= _('Allow edits from maintainers.') = _('Allow commits from members who can merge to the target branch.')
= link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access') = link_to 'About this feature', help_page_path('user/project/merge_requests/allow_collaboration')
.form-text.text-muted .form-text.text-muted
= allow_maintainer_push_unavailable_reason(issuable) = allow_collaboration_unavailable_reason(issuable)
- unless can?(current_user, :push_code, @project) - unless can?(current_user, :push_code, @project)
.inline.prepend-left-10 .inline.prepend-left-10
- if @project.branch_allows_maintainer_push?(current_user, selected_branch) - if @project.branch_allows_collaboration?(current_user, selected_branch)
= commit_in_single_accessible_branch = commit_in_single_accessible_branch
- else - else
= commit_in_fork_help = commit_in_fork_help
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
.float-right .float-right
= button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do = button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do
= _('Accept terms') = _('Accept terms')
- else
.pull-right
= link_to root_path, class: 'btn btn-success prepend-left-8' do
= _('Continue')
- if can?(current_user, :decline_terms, @term) - if can?(current_user, :decline_terms, @term)
.float-right .float-right
= button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do = button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do
......
---
title: Add variables to POST api/v4/projects/:id/pipeline
merge_request: 19124
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Rephrasing Merge Request's 'allow edits from maintainer' functionality
merge_request: 19061
author:
type: deprecated
---
title: Fix repository archive generation when hashed storage is enabled
merge_request: 19441
author:
type: fixed
---
title: Add flash notice if user has already accepted terms and allow users to continue
to root path
merge_request: 19156
author:
type: changed
---
title: Fix an N+1 when loading user avatars
merge_request:
author:
type: performance
---
title: Update runner cached informations without performing validations
merge_request:
author:
type: performance
---
title: 'Rails 5 fix unknown keywords: changes, key_id, project, gl_repository, action,
secret_token, protocol'
merge_request: 19466
author: Jasper Maes
type: fixed
---
title: Add migration to disable the usage of DSA keys
merge_request: 19299
author:
type: other
---
title: Eliminate N+1 queries with authors and push_data_payload in Events API
merge_request:
author:
type: performance
class RenameMergeRequestsAllowMaintainerToPush < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :merge_requests, :allow_maintainer_to_push, :allow_collaboration
end
def down
cleanup_concurrent_column_rename :merge_requests, :allow_collaboration, :allow_maintainer_to_push
end
end
class AddIndexToStagesPosition < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_stages, [:pipeline_id, :position]
end
def down
remove_concurrent_index :ci_stages, [:pipeline_id, :position]
end
end
class ChangeDefaultValueForDsaKeyRestriction < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
change_column :application_settings, :dsa_key_restriction, :integer, null: false,
default: -1
execute("UPDATE application_settings SET dsa_key_restriction = -1")
end
def down
change_column :application_settings, :dsa_key_restriction, :integer, null: false,
default: 0
end
end
class CleanupMergeRequestsAllowMaintainerToPushRename < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :merge_requests, :allow_maintainer_to_push, :allow_collaboration
end
def down
rename_column_concurrently :merge_requests, :allow_collaboration, :allow_maintainer_to_push
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180529152628) do ActiveRecord::Schema.define(version: 20180531220618) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -110,7 +110,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do ...@@ -110,7 +110,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do
t.text "shared_runners_text_html" t.text "shared_runners_text_html"
t.text "after_sign_up_text_html" t.text "after_sign_up_text_html"
t.integer "rsa_key_restriction", default: 0, null: false t.integer "rsa_key_restriction", default: 0, null: false
t.integer "dsa_key_restriction", default: 0, null: false t.integer "dsa_key_restriction", default: -1, null: false
t.integer "ecdsa_key_restriction", default: 0, null: false t.integer "ecdsa_key_restriction", default: 0, null: false
t.integer "ed25519_key_restriction", default: 0, null: false t.integer "ed25519_key_restriction", default: 0, null: false
t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_enabled", default: true, null: false
...@@ -520,6 +520,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do ...@@ -520,6 +520,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do
end end
add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree
add_index "ci_stages", ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position", using: :btree
add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree add_index "ci_stages", ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree
add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree add_index "ci_stages", ["project_id"], name: "index_ci_stages_on_project_id", using: :btree
...@@ -1229,7 +1230,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do ...@@ -1229,7 +1230,7 @@ ActiveRecord::Schema.define(version: 20180529152628) do
t.boolean "discussion_locked" t.boolean "discussion_locked"
t.integer "latest_merge_request_diff_id" t.integer "latest_merge_request_diff_id"
t.string "rebase_commit_sha" t.string "rebase_commit_sha"
t.boolean "allow_maintainer_to_push" t.boolean "allow_collaboration"
t.boolean "squash", default: false, null: false t.boolean "squash", default: false, null: false
end end
......
...@@ -651,7 +651,8 @@ POST /projects/:id/merge_requests ...@@ -651,7 +651,8 @@ POST /projects/:id/merge_requests
| `labels` | string | no | Labels for MR as a comma-separated list | | `labels` | string | no | Labels for MR as a comma-separated list |
| `milestone_id` | integer | no | The global ID of a milestone | | `milestone_id` | integer | no | The global ID of a milestone |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | | `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch |
| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration |
| `squash` | boolean | no | Squash commits into a single commit when merging | | `squash` | boolean | no | Squash commits into a single commit when merging |
```json ```json
...@@ -709,6 +710,7 @@ POST /projects/:id/merge_requests ...@@ -709,6 +710,7 @@ POST /projects/:id/merge_requests
"squash": false, "squash": false,
"web_url": "http://example.com/example/example/merge_requests/1", "web_url": "http://example.com/example/example/merge_requests/1",
"discussion_locked": false, "discussion_locked": false,
"allow_collaboration": false,
"allow_maintainer_to_push": false, "allow_maintainer_to_push": false,
"time_stats": { "time_stats": {
"time_estimate": 0, "time_estimate": 0,
...@@ -741,7 +743,8 @@ PUT /projects/:id/merge_requests/:merge_request_iid ...@@ -741,7 +743,8 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `squash` | boolean | no | Squash commits into a single commit when merging | | `squash` | boolean | no | Squash commits into a single commit when merging |
| `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. | | `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | | `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch |
| `allow_maintainer_to_push` | boolean | no | Deprecated, see allow_collaboration |
Must include at least one non-required attribute from above. Must include at least one non-required attribute from above.
...@@ -799,6 +802,7 @@ Must include at least one non-required attribute from above. ...@@ -799,6 +802,7 @@ Must include at least one non-required attribute from above.
"squash": false, "squash": false,
"web_url": "http://example.com/example/example/merge_requests/1", "web_url": "http://example.com/example/example/merge_requests/1",
"discussion_locked": false, "discussion_locked": false,
"allow_collaboration": false,
"allow_maintainer_to_push": false, "allow_maintainer_to_push": false,
"time_stats": { "time_stats": {
"time_estimate": 0, "time_estimate": 0,
......
...@@ -102,6 +102,7 @@ POST /projects/:id/pipeline ...@@ -102,6 +102,7 @@ POST /projects/:id/pipeline
|------------|---------|----------|---------------------| |------------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `ref` | string | yes | Reference to commit | | `ref` | string | yes | Reference to commit |
| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] |
``` ```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
......
...@@ -16,7 +16,7 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -16,7 +16,7 @@ are very appreciative of the work done by translators and proofreaders!
- Dutch - Dutch
- Esperanto - Esperanto
- French - French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai) - Davy Defaud - [GitLab](https://gitlab.com/DevDef), [Crowdin](https://crowdin.com/profile/DevDef)
- German - German
- Indonesian - Indonesian
- Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm) - Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
......
...@@ -143,6 +143,24 @@ docker login registry.example.com -u <your_username> -p <your_access_token> ...@@ -143,6 +143,24 @@ docker login registry.example.com -u <your_username> -p <your_access_token>
for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues
there. there.
#### Enable the registry debug server
The optional debug server can be enabled by setting the registry debug address
in your `gitlab.rb` configuration.
```ruby
registry['debug_addr'] = "localhost:5001"
```
After adding the setting, [reconfigure] GitLab to apply the change.
Use curl to request debug output from the debug server:
```bash
curl localhost:5001/debug/health
curl localhost:5001/debug/vars
```
### Advanced Troubleshooting ### Advanced Troubleshooting
>**NOTE:** The following section is only recommended for experts. >**NOTE:** The following section is only recommended for experts.
...@@ -275,3 +293,4 @@ Once the right permissions were set, the error will go away. ...@@ -275,3 +293,4 @@ Once the right permissions were set, the error will go away.
[docker-docs]: https://docs.docker.com/engine/userguide/intro/ [docker-docs]: https://docs.docker.com/engine/userguide/intro/
[pat]: ../profile/personal_access_tokens.md [pat]: ../profile/personal_access_tokens.md
[pdt]: ../project/deploy_tokens/index.md [pdt]: ../project/deploy_tokens/index.md
[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
\ No newline at end of file
# Allow collaboration on merge requests across forks
> [Introduced][ce-17395] in GitLab 10.6.
This feature is available for merge requests across forked projects that are
publicly accessible. It makes it easier for members of projects to
collaborate on merge requests across forks.
When enabled for a merge request, members with merge access to the target
branch of the project will be granted write permissions to the source branch
of the merge request.
The feature can only be enabled by users who already have push access to the
source project, and only lasts while the merge request is open.
Enable this functionality while creating or editing a merge request:
![Enable collaboration](./img/allow_collaboration.png)
[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395
...@@ -28,7 +28,7 @@ With GitLab merge requests, you can: ...@@ -28,7 +28,7 @@ With GitLab merge requests, you can:
- Enable [fast-forward merge requests](#fast-forward-merge-requests) - Enable [fast-forward merge requests](#fast-forward-merge-requests)
- Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch - Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch
- [Create new merge requests by email](#create-new-merge-requests-by-email) - [Create new merge requests by email](#create-new-merge-requests-by-email)
- Allow maintainers of the target project to push directly to the fork by [allowing edits from maintainers](maintainer_access.md) - [Allow collaboration](allow_collaboration.md) so members of the target project can push directly to the fork
- [Squash and merge](squash_and_merge.md) for a cleaner commit history - [Squash and merge](squash_and_merge.md) for a cleaner commit history
With **[GitLab Enterprise Edition][ee]**, you can also: With **[GitLab Enterprise Edition][ee]**, you can also:
......
# Allow maintainer pushes for merge requests across forks This document was moved to [another location](allow_collaboration.md).
> [Introduced][ce-17395] in GitLab 10.6.
This feature is available for merge requests across forked projects that are
publicly accessible. It makes it easier for maintainers of projects to
collaborate on merge requests across forks.
When enabled for a merge request, members with merge access to the target
branch of the project will be granted write permissions to the source branch
of the merge request.
The feature can only be enabled by users who already have push access to the
source project, and only lasts while the merge request is open.
Enable this functionality while creating a merge request:
![Enable maintainer edits](./img/allow_maintainer_push.png)
[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395
...@@ -42,7 +42,7 @@ to secure them. ...@@ -42,7 +42,7 @@ to secure them.
Your files live in a project [repository](../repository/index.md) on GitLab. Your files live in a project [repository](../repository/index.md) on GitLab.
[GitLab CI](../../../ci/README.md) picks up those files and makes them available at, typically, [GitLab CI](../../../ci/README.md) picks up those files and makes them available at, typically,
`http://<username>.gitlab.io/<projectname>`. Please read through the docs on `https://<username>.gitlab.io/<projectname>`. Please read through the docs on
[GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain) for more info. [GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain) for more info.
## Explore GitLab Pages ## Explore GitLab Pages
......
...@@ -559,7 +559,9 @@ module API ...@@ -559,7 +559,9 @@ module API
expose :discussion_locked expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch
expose :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? }
# Deprecated
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
expose :web_url do |merge_request, options| expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request) Gitlab::UrlBuilder.build(merge_request)
......
...@@ -17,6 +17,7 @@ module API ...@@ -17,6 +17,7 @@ module API
def present_events(events) def present_events(events)
events = events.reorder(created_at: params[:sort]) events = events.reorder(created_at: params[:sort])
.with_associations
present paginate(events), with: Entities::Event present paginate(events), with: Entities::Event
end end
......
...@@ -162,7 +162,8 @@ module API ...@@ -162,7 +162,8 @@ module API
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :labels, type: String, desc: 'Comma-separated list of label names' optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
optional :allow_maintainer_to_push, type: Boolean, desc: 'Whether a maintainer of the target project can push to the source project' optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
use :optional_params_ee use :optional_params_ee
......
...@@ -41,15 +41,20 @@ module API ...@@ -41,15 +41,20 @@ module API
end end
params do params do
requires :ref, type: String, desc: 'Reference' requires :ref, type: String, desc: 'Reference'
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end end
post ':id/pipeline' do post ':id/pipeline' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124')
authorize! :create_pipeline, user_project authorize! :create_pipeline, user_project
pipeline_params = declared_params(include_missing: false)
.merge(variables_attributes: params[:variables])
.except(:variables)
new_pipeline = Ci::CreatePipelineService.new(user_project, new_pipeline = Ci::CreatePipelineService.new(user_project,
current_user, current_user,
declared_params(include_missing: false)) pipeline_params)
.execute(:api, ignore_skip_ci: true, save_on_errors: false) .execute(:api, ignore_skip_ci: true, save_on_errors: false)
if new_pipeline.persisted? if new_pipeline.persisted?
......
module Backup
Error = Class.new(StandardError)
end
...@@ -44,7 +44,7 @@ module Backup ...@@ -44,7 +44,7 @@ module Backup
end end
report_success(success) report_success(success)
abort 'Backup failed' unless success raise Backup::Error, 'Backup failed' unless success
end end
def restore def restore
...@@ -72,7 +72,7 @@ module Backup ...@@ -72,7 +72,7 @@ module Backup
end end
report_success(success) report_success(success)
abort 'Restore failed' unless success abort Backup::Error, 'Restore failed' unless success
end end
protected protected
......
...@@ -26,7 +26,7 @@ module Backup ...@@ -26,7 +26,7 @@ module Backup
unless status.zero? unless status.zero?
puts output puts output
abort 'Backup failed' raise Backup::Error, 'Backup failed'
end end
run_pipeline!([%W(tar --exclude=lost+found -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) run_pipeline!([%W(tar --exclude=lost+found -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600])
...@@ -39,7 +39,11 @@ module Backup ...@@ -39,7 +39,11 @@ module Backup
def restore def restore
backup_existing_files_dir backup_existing_files_dir
run_pipeline!([%w(gzip -cd), %W(tar --unlink-first --recursive-unlink -C #{app_files_dir} -xf -)], in: backup_tarball) run_pipeline!([%w(gzip -cd), %W(#{tar} --unlink-first --recursive-unlink -C #{app_files_dir} -xf -)], in: backup_tarball)
end
def tar
system(*%w[gtar --version], out: '/dev/null') ? 'gtar' : 'tar'
end end
def backup_existing_files_dir def backup_existing_files_dir
...@@ -61,7 +65,7 @@ module Backup ...@@ -61,7 +65,7 @@ module Backup
def run_pipeline!(cmd_list, options = {}) def run_pipeline!(cmd_list, options = {})
status_list = Open3.pipeline(*cmd_list, options) status_list = Open3.pipeline(*cmd_list, options)
abort 'Backup failed' unless status_list.compact.all?(&:success?) raise Backup::Error, 'Backup failed' unless status_list.compact.all?(&:success?)
end end
end end
end end
...@@ -27,7 +27,7 @@ module Backup ...@@ -27,7 +27,7 @@ module Backup
progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "creating archive #{tar_file} failed".color(:red) puts "creating archive #{tar_file} failed".color(:red)
abort 'Backup failed' raise Backup::Error, 'Backup failed'
end end
upload upload
...@@ -52,7 +52,7 @@ module Backup ...@@ -52,7 +52,7 @@ module Backup
progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "uploading backup to #{remote_directory} failed".color(:red) puts "uploading backup to #{remote_directory} failed".color(:red)
abort 'Backup failed' raise Backup::Error, 'Backup failed'
end end
end end
...@@ -66,7 +66,7 @@ module Backup ...@@ -66,7 +66,7 @@ module Backup
progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "deleting tmp directory '#{dir}' failed".color(:red) puts "deleting tmp directory '#{dir}' failed".color(:red)
abort 'Backup failed' raise Backup::Error, 'Backup failed'
end end
end end
end end
......
...@@ -17,7 +17,10 @@ module Backup ...@@ -17,7 +17,10 @@ module Backup
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
progress.print " * #{display_repo_path(project)} ... " progress.print " * #{display_repo_path(project)} ... "
path_to_project_repo = path_to_repo(project)
path_to_project_repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
path_to_repo(project)
end
path_to_project_bundle = path_to_bundle(project) path_to_project_bundle = path_to_bundle(project)
# Create namespace dir or hashed path if missing # Create namespace dir or hashed path if missing
...@@ -51,7 +54,9 @@ module Backup ...@@ -51,7 +54,9 @@ module Backup
end end
wiki = ProjectWiki.new(project) wiki = ProjectWiki.new(project)
path_to_wiki_repo = path_to_repo(wiki) path_to_wiki_repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
path_to_repo(wiki)
end
path_to_wiki_bundle = path_to_bundle(wiki) path_to_wiki_bundle = path_to_bundle(wiki)
if File.exist?(path_to_wiki_repo) if File.exist?(path_to_wiki_repo)
...@@ -111,7 +116,9 @@ module Backup ...@@ -111,7 +116,9 @@ module Backup
# TODO: Need to find a way to do this for gitaly # TODO: Need to find a way to do this for gitaly
# Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195 # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195
in_path(path_to_tars(project)) do |dir| in_path(path_to_tars(project)) do |dir|
path_to_project_repo = path_to_repo(project) path_to_project_repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
path_to_repo(project)
end
cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir}) cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir})
output, status = Gitlab::Popen.popen(cmd) output, status = Gitlab::Popen.popen(cmd)
......
...@@ -5,23 +5,47 @@ module Gitlab ...@@ -5,23 +5,47 @@ module Gitlab
module Pipeline module Pipeline
# Class for preloading data associated with pipelines such as commit # Class for preloading data associated with pipelines such as commit
# authors. # authors.
module Preloader class Preloader
def self.preload(pipelines) def self.preload!(pipelines)
# This ensures that all the pipeline commits are eager loaded before we ##
# start using them. # This preloads all commits at once, because `Ci::Pipeline#commit` is
# using a lazy batch loading, what results in only one batched Gitaly
# call.
#
pipelines.each(&:commit) pipelines.each(&:commit)
pipelines.each do |pipeline| pipelines.each do |pipeline|
# This preloads the author of every commit. We're using "lazy_author" self.new(pipeline).tap do |preloader|
# here since "author" immediately loads the data on the first call. preloader.preload_commit_authors
pipeline.commit.try(:lazy_author) preloader.preload_pipeline_warnings
preloader.preload_stages_warnings
# This preloads the number of warnings for every pipeline, ensuring end
# that Ci::Pipeline#has_warnings? doesn't execute any additional
# queries.
pipeline.number_of_warnings
end end
end end
def initialize(pipeline)
@pipeline = pipeline
end
def preload_commit_authors
# This also preloads the author of every commit. We're using "lazy_author"
# here since "author" immediately loads the data on the first call.
@pipeline.commit.try(:lazy_author)
end
def preload_pipeline_warnings
# This preloads the number of warnings for every pipeline, ensuring
# that Ci::Pipeline#has_warnings? doesn't execute any additional
# queries.
@pipeline.number_of_warnings
end
def preload_stages_warnings
# This preloads the number of warnings for every stage, ensuring
# that Ci::Stage#has_warnings? doesn't execute any additional
# queries.
@pipeline.stages.each { |stage| stage.number_of_warnings }
end
end end
end end
end end
......
...@@ -8,7 +8,9 @@ module Gitlab ...@@ -8,7 +8,9 @@ module Gitlab
end end
def details_path def details_path
project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name) project_pipeline_path(subject.pipeline.project,
subject.pipeline,
anchor: subject.name)
end end
def has_action? def has_action?
......
...@@ -109,7 +109,7 @@ module Gitlab ...@@ -109,7 +109,7 @@ module Gitlab
end end
def ==(other) def ==(other)
path == other.path [storage, relative_path] == [other.storage, other.relative_path]
end end
def path def path
...@@ -395,12 +395,12 @@ module Gitlab ...@@ -395,12 +395,12 @@ module Gitlab
nil nil
end end
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:) def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
ref ||= root_ref ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref) commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil? return {} if commit.nil?
prefix = archive_prefix(ref, commit.id, append_sha: append_sha) prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha)
{ {
'ArchivePrefix' => prefix, 'ArchivePrefix' => prefix,
...@@ -412,16 +412,12 @@ module Gitlab ...@@ -412,16 +412,12 @@ module Gitlab
# This is both the filename of the archive (missing the extension) and the # This is both the filename of the archive (missing the extension) and the
# name of the top-level member of the archive under which all files go # name of the top-level member of the archive under which all files go
# def archive_prefix(ref, sha, project_path, append_sha:)
# FIXME: The generated prefix is incorrect for projects with hashed
# storage enabled
def archive_prefix(ref, sha, append_sha:)
append_sha = (ref != sha) if append_sha.nil? append_sha = (ref != sha) if append_sha.nil?
project_name = self.name.chomp('.git')
formatted_ref = ref.tr('/', '-') formatted_ref = ref.tr('/', '-')
prefix_segments = [project_name, formatted_ref] prefix_segments = [project_path, formatted_ref]
prefix_segments << sha if append_sha prefix_segments << sha if append_sha
prefix_segments.join('-') prefix_segments.join('-')
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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