Commit fa7dc5dc authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into snippets-dispatcher-refactor

parents c0fdcb9b 685780d5
......@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2'
# OAuth
gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.3.1'
# Health check
gem 'health_check', '~> 2.6.0'
......
......@@ -580,8 +580,6 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.2)
ast (~> 2.3)
parslet (1.5.0)
......@@ -1117,7 +1115,6 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
paranoia (~> 2.3.1)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
......
......@@ -12,7 +12,6 @@ import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
import groupsSelect from './groups_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
......@@ -40,7 +39,6 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import ShortcutsWiki from './shortcuts_wiki';
import Pipelines from './pipelines';
import BlobViewer from './blob/viewer/index';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select';
......@@ -49,11 +47,9 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
import Star from './star';
import TreeView from './tree';
import VersionCheckImage from './version_check_image';
import Wikis from './wikis';
import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import PerformanceBar from './performance_bar';
import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters';
......@@ -145,9 +141,6 @@ import Activities from './activities';
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
switch (page) {
case 'profiles:preferences:show':
initExperimentalFlags();
break;
case 'sessions:new':
import('./pages/sessions/new')
.then(callDefault)
......@@ -403,23 +396,16 @@ import Activities from './activities';
break;
case 'projects:pipelines:new':
case 'projects:pipelines:create':
new NewBranchForm($('.js-new-pipeline-form'));
import('./pages/projects/pipelines/new')
.then(callDefault)
.catch(fail);
break;
case 'projects:pipelines:builds':
case 'projects:pipelines:failures':
case 'projects:pipelines:show':
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
new Pipelines({
initTabs: true,
pipelineStatusUrl,
tabsOptions: {
action: controllerAction,
defaultAction: 'pipelines',
parentEl: '.pipelines-tabs',
},
});
import('./pages/projects/pipelines/builds')
.then(callDefault)
.catch(fail);
break;
case 'groups:activity':
new Activities();
......@@ -441,11 +427,9 @@ import Activities from './activities';
new UsersSelect();
break;
case 'projects:project_members:index':
memberExpirationDate('.js-access-expiration-date-groups');
groupsSelect();
memberExpirationDate();
new Members();
new UsersSelect();
import('./pages/projects/project_members/')
.then(callDefault)
.catch(fail);
break;
case 'groups:new':
case 'groups:create':
......@@ -537,7 +521,9 @@ import Activities from './activities';
shortcut_handler = true;
break;
case 'help:index':
VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
import('./pages/help')
.then(callDefault)
.catch(fail);
break;
case 'search:show':
import('./pages/search/show')
......@@ -582,14 +568,16 @@ import Activities from './activities';
case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
.then(callDefault)
.catch(fail);
break;
case 'admin:impersonation_tokens:index':
import('./pages/admin/impersonation_tokens')
.then(callDefault)
.catch(fail);
break;
case 'profiles:personal_access_tokens:index':
new DueDateSelectors();
break;
case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
.then(cluster => new cluster.default()) // eslint-disable-line new-cap
......@@ -675,8 +663,9 @@ import Activities from './activities';
new UserCallout();
break;
case 'profiles':
new NotificationsForm();
notificationsDropdown();
import('./pages/profiles/index/')
.then(callDefault)
.catch(fail);
break;
case 'projects':
new Project();
......
import VersionCheckImage from '../../version_check_image';
export default () => VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
import NotificationsForm from '../../../notifications_form';
import notificationsDropdown from '../../../notifications_dropdown';
export default () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
};
import DueDateSelectors from '../../../due_date_select';
export default () => new DueDateSelectors();
import Pipelines from '../../../../pipelines';
export default () => {
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
new Pipelines({ // eslint-disable-line no-new
initTabs: true,
pipelineStatusUrl,
tabsOptions: {
action: controllerAction,
defaultAction: 'pipelines',
parentEl: '.pipelines-tabs',
},
});
};
import NewBranchForm from '../../../../new_branch_form';
export default () => {
new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new
};
import memberExpirationDate from '../../../member_expiration_date';
import UsersSelect from '../../../users_select';
import groupsSelect from '../../../groups_select';
import Members from '../../../members';
export default () => {
memberExpirationDate('.js-access-expiration-date-groups');
groupsSelect();
memberExpirationDate();
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
};
<script>
import tooltip from '../directives/tooltip';
/**
* Falls back to the code used in `copy_to_clipboard.js`
*/
export default {
name: 'ClipboardButton',
directives: {
tooltip,
},
props: {
text: {
type: String,
......@@ -14,6 +18,16 @@
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
tooltipContainer: {
type: [String, Boolean],
required: false,
default: false,
},
},
};
</script>
......@@ -22,8 +36,11 @@
<button
type="button"
class="btn btn-transparent btn-clipboard"
:data-title="title"
:title="title"
:data-clipboard-text="text"
v-tooltip
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
>
<i
aria-hidden="true"
......
......@@ -65,6 +65,7 @@ class Admin::RunnersController < Admin::ApplicationController
else
Project.all
end
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
......
......@@ -8,6 +8,7 @@ module GroupTree
# Only show root groups if no parent-id is given
groups.where(parent_id: params[:parent_id])
end
@groups = @groups.with_selects_for_list(archived: params[:archived])
.sort(@sort = params[:sort])
.page(params[:page])
......
......@@ -32,6 +32,7 @@ module RoutableActions
if canonical_path.casecmp(requested_full_path) != 0
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end
redirect_to build_canonical_path(routable)
end
end
......
......@@ -12,6 +12,7 @@ class MetricsController < ActionController::Base
)
"# Metrics are disabled, see: #{help_page}\n"
end
render text: response, content_type: 'text/plain; version=0.0.4'
end
......
......@@ -83,6 +83,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if ticket
handle_service_ticket oauth['provider'], ticket
end
handle_omniauth
end
......@@ -90,6 +91,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if params['sid']
handle_service_ticket oauth['provider'], params['sid']
end
handle_omniauth
end
......@@ -124,6 +126,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user)
......
......@@ -150,6 +150,7 @@ class Projects::BlobController < Projects::ApplicationController
if params[:file].present?
params[:file_name] = params[:file].original_filename
end
File.join(@path, params[:file_name])
elsif params[:file_path].present?
params[:file_path]
......
......@@ -27,6 +27,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end
redirect_to_repository_settings(@project)
end
......
......@@ -21,6 +21,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe
end
redirect_to project_settings_integrations_path(@project)
end
......
......@@ -48,6 +48,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
else
[]
end
@diff_notes_disabled = true
@environment = @merge_request.environments_for(current_user).last
......
......@@ -203,6 +203,7 @@ class ProjectsController < Projects::ApplicationController
else
flash[:alert] = _("Project export could not be deleted.")
end
redirect_to(edit_project_path(@project))
end
......
......@@ -28,6 +28,7 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
# hide the signed-in notification
flash[:notice] = nil
log_audit_event(current_user, resource, with: authentication_method)
......
......@@ -63,6 +63,7 @@ class GroupDescendantsFinder
groups_table = Group.arel_table
visible_to_user = groups_table[:visibility_level]
.in(Gitlab::VisibilityLevel.levels_for_user(current_user))
if current_user
authorized_groups = GroupsFinder.new(current_user,
all_available: false)
......@@ -115,6 +116,7 @@ class GroupDescendantsFinder
else
direct_child_groups
end
groups.with_selects_for_list(archived: params[:archived]).order_by(sort)
end
......@@ -140,6 +142,7 @@ class GroupDescendantsFinder
else
direct_child_projects
end
projects.with_route.order_by(sort)
end
......
......@@ -34,6 +34,7 @@ class GroupProjectsFinder < ProjectsFinder
else
collection_without_user
end
union(projects)
end
......
......@@ -203,6 +203,7 @@ module MarkupHelper
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
......
......@@ -12,6 +12,7 @@ module NavHelper
current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') ||
current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true'
%w[page-gutter right-sidebar-collapsed]
else
......
......@@ -89,6 +89,7 @@ module SnippetsHelper
snippet_chunk = [lined_content[line_number]]
snippet_start_line = line_number
end
last_line = line_number
end
# Add final chunk to chunk array
......
......@@ -58,6 +58,7 @@ module SubmoduleHelper
url_no_dotgit = url.chomp('.git')
return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/',
project].join('')
url_with_dotgit = url_no_dotgit + '.git'
url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
end
......
......@@ -30,6 +30,7 @@ module TodosHelper
else
todo.target_reference
end
link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end
......
......@@ -418,6 +418,7 @@ class ApplicationSetting < ActiveRecord::Base
super(group_full_path)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
return
end
......
......@@ -2,8 +2,9 @@ module Ci
class PipelineSchedule < ActiveRecord::Base
extend Gitlab::Ci::Model
include Importable
include IgnorableColumn
acts_as_paranoid
ignore_column :deleted_at
belongs_to :project
belongs_to :owner, class_name: 'User'
......
module Ci
class Trigger < ActiveRecord::Base
extend Gitlab::Ci::Model
include IgnorableColumn
acts_as_paranoid
ignore_column :deleted_at
belongs_to :project
belongs_to :owner, class_name: "User"
......
......@@ -10,7 +10,6 @@ module InternalId
if iid.blank?
parent = project || group
records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1
......
......@@ -314,6 +314,7 @@ module Issuable
includes = []
includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any?
notes.includes(includes)
else
......
......@@ -25,6 +25,7 @@ module LoadedInGroupList
base_count = projects.project(Arel.star.count.as('preloaded_project_count'))
.where(projects[:namespace_id].eq(namespaces[:id]))
if archived == 'only'
base_count.where(projects[:archived].eq(true))
elsif Gitlab::Utils.to_boolean(archived)
......
......@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base
include ThrottledTouch
include IgnorableColumn
ignore_column :assignee_id, :branch_name
ignore_column :assignee_id, :branch_name, :deleted_at
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......@@ -78,8 +78,6 @@ class Issue < ActiveRecord::Base
end
end
acts_as_paranoid
class << self
alias_method :in_parents, :in_projects
end
......
......@@ -132,6 +132,7 @@ class Label < ActiveRecord::Base
else
priorities.find_by(project: project)
end
priority.try(:priority)
end
......
......@@ -11,7 +11,8 @@ class MergeRequest < ActiveRecord::Base
include Gitlab::Utils::StrongMemoize
ignore_column :locked_at,
:ref_fetched
:ref_fetched,
:deleted_at
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
......@@ -150,8 +151,6 @@ class MergeRequest < ActiveRecord::Base
after_save :keep_around_commit
acts_as_paranoid
def self.reference_prefix
'!'
end
......@@ -794,6 +793,7 @@ class MergeRequest < ActiveRecord::Base
if !include_description && closes_issues_references.present?
message << "Closes #{closes_issues_references.to_sentence}"
end
message << "#{description}" if include_description && description.present?
message << "See merge request #{to_reference(full: true)}"
......
......@@ -49,6 +49,7 @@ class MergeRequestDiff < ActiveRecord::Base
ensure_commit_shas
save_commits
save_diffs
save
keep_around_commits
end
......@@ -56,7 +57,6 @@ class MergeRequestDiff < ActiveRecord::Base
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
self.base_commit_sha ||= find_base_sha
save
end
# Override head_commit_sha to keep compatibility with merge request diff
......@@ -195,7 +195,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
merge_request_diff_commits.size
super || merge_request_diff_commits.size
end
private
......@@ -264,13 +264,16 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
update(new_attributes)
assign_attributes(new_attributes)
end
def save_commits
MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
merge_request_diff_commits.reload
# merge_request_diff_commits.reload is preferred way to reload associated
# objects but it returns cached result for some reason in this case
commits = merge_request_diff_commits(true)
self.commits_count = commits.size
end
def repository
......
class Namespace < ActiveRecord::Base
acts_as_paranoid without_default_scope: true
include CacheMarkdownField
include Sortable
include Gitlab::ShellAdapter
......@@ -10,6 +8,9 @@ class Namespace < ActiveRecord::Base
include AfterCommitQueue
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
include IgnorableColumn
ignore_column :deleted_at
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
......@@ -221,12 +222,6 @@ class Namespace < ActiveRecord::Base
has_parent?
end
def soft_delete_without_removing_associations
# We can't use paranoia's `#destroy` since this will hard-delete projects.
# Project uses `pending_delete` instead of the acts_as_paranoia gem.
self.deleted_at = Time.now
end
private
def refresh_access_of_projects_invited_groups
......
......@@ -224,6 +224,7 @@ module Network
space_base = parents.first.space
end
end
space_base
end
......
......@@ -9,6 +9,7 @@ class NotificationRecipient
group: nil,
skip_read_ability: false
)
unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}"
end
......
......@@ -633,6 +633,7 @@ class Project < ActiveRecord::Base
project_import_data.data ||= {}
project_import_data.data = project_import_data.data.merge(data)
end
if credentials
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
......
......@@ -110,6 +110,7 @@ class HipchatService < Service
message = ""
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
message << "pushed new #{ref_type} <a href=\""\
"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
......
......@@ -1014,6 +1014,7 @@ class Repository
else
cache.fetch(key, &block)
end
instance_variable_set(ivar, value)
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
# Even if the above `#exists?` check passes these errors might still
......
......@@ -250,6 +250,7 @@ class Service < ActiveRecord::Base
teamcity
microsoft_teams
]
if Rails.env.development?
service_names += %w[mock_ci mock_deployment mock_monitoring]
end
......
......@@ -6,7 +6,6 @@ class IssueEntity < IssuableEntity
expose :updated_by_id
expose :created_at
expose :updated_at
expose :deleted_at
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
expose :lock_version
......
......@@ -16,6 +16,7 @@ class CreateDeploymentService
ActiveRecord::Base.transaction do
environment.external_url = expanded_environment_url if
expanded_environment_url
environment.fire_state_event(action)
return unless environment.save
......
module Groups
class DestroyService < Groups::BaseService
def async_execute
group.soft_delete_without_removing_associations
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
......@@ -23,7 +22,7 @@ module Groups
group.chat_team&.remove_mattermost_team(current_user)
group.really_destroy!
group.destroy
end
end
end
......@@ -53,7 +53,7 @@ module Users
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
user_data = user.destroy
namespace.really_destroy!
namespace.destroy
user_data
end
......
......@@ -4,7 +4,7 @@
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title
%h1.project-title.qa-project-name
= @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false)
......
......@@ -9,7 +9,7 @@
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1}
- else
.input-group-addon.static-namespace
......
......@@ -30,6 +30,7 @@
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
#{ _('New file') }
- unless @project.empty_repo?
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
......
......@@ -27,7 +27,7 @@
Edit
- if @project.group
= link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "Promoting this milestone will make it available for all projects inside the group. Existing project milestones with the same name will be merged. Are you sure?", toggle: "tooltip" }, method: :post do
= link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "You are about to promote #{@milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
Promote
- if @milestone.active?
......
......@@ -7,7 +7,7 @@
%span
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.btn.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown' } }
%a#clone-dropdown.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span
= default_clone_protocol.upcase
= icon('caret-down')
......
......@@ -77,7 +77,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
= link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
= link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
......
......@@ -51,7 +51,7 @@
\
- if @project.group
= link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "Promoting this milestone will make it available for all projects inside the group. Existing project milestones with the same name will be merged. Are you sure?", toggle: "tooltip" }, method: :post do
= link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "You are about to promote #{milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
Promote
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
......
......@@ -4,7 +4,7 @@ class GroupDestroyWorker
def perform(group_id, user_id)
begin
group = Group.with_deleted.find(group_id)
group = Group.find(group_id)
rescue ActiveRecord::RecordNotFound
return
end
......
......@@ -13,6 +13,7 @@ class PagesWorker
if result[:status] == :success
result = Projects::UpdatePagesConfigurationService.new(build.project).execute
end
result
end
......
---
title: Adds Rubocop rule for line break around conditionals
merge_request: 15739
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Store number of commits in merge_request_diffs table.
merge_request:
author:
type: performance
---
title: Hide new branch and tag links for projects with an empty repo
merge_request:
author:
type: fixed
---
title: Remove soft removals related code
merge_request: 15789
author:
type: changed
......@@ -61,6 +61,7 @@ module Gitlab
# - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
# - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook)
......@@ -75,6 +76,7 @@ module Gitlab
key
otp_attempt
sentry_dsn
trace
variables
)
......@@ -149,6 +151,7 @@ module Gitlab
caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
caching_config_hash[:pool_timeout] = 1
end
config.cache_store = :redis_store, caching_config_hash
config.active_record.raise_in_transactional_callbacks = true
......
......@@ -68,6 +68,7 @@ class Settings < Settingslogic
end
values.delete_if { |value| value.nil? }
end
values
end
......@@ -78,6 +79,7 @@ class Settings < Settingslogic
if current.is_a? String
value = modul.const_get(current.upcase) rescue default
end
value
end
......
......@@ -241,6 +241,7 @@ Devise.setup do |config|
true
end
end
if provider['name'] == 'authentiq'
provider['args'][:remote_sign_out_handler] = lambda do |request|
authentiq_session = request.params['sid']
......
......@@ -2,6 +2,7 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Re
Peek.into Peek::Views::Host
Peek.into Peek::Views::PerformanceBar
if Gitlab::Database.mysql?
require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client
......@@ -11,6 +12,7 @@ else
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
end
Peek.into PEEK_DB_VIEW
Peek.into Peek::Views::Redis
Peek.into Peek::Views::Sidekiq
......
......@@ -23,6 +23,7 @@ class CreateForkNetworks < ActiveRecord::Migration
if foreign_keys_for(:fork_networks, :root_project_id).any?
remove_foreign_key :fork_networks, column: :root_project_id
end
drop_table :fork_networks
end
end
......@@ -21,6 +21,7 @@ class CreateForkNetworkMembers < ActiveRecord::Migration
if foreign_keys_for(:fork_network_members, :forked_from_project_id).any?
remove_foreign_key :fork_network_members, column: :forked_from_project_id
end
drop_table :fork_network_members
end
end
......@@ -9,6 +9,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
return unless Gitlab::Database.postgresql?
disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.5
# Allow us to hot-patch the index manually ahead of the migration
execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
......@@ -21,6 +22,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
return unless Gitlab::Database.postgresql?
disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.2
execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
else
......
class AddCommitsCountToMergeRequestDiff < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'AddMergeRequestDiffCommitsCount'.freeze
BATCH_SIZE = 5000
DELAY_INTERVAL = 5.minutes.to_i
class MergeRequestDiff < ActiveRecord::Base
self.table_name = 'merge_request_diffs'
include ::EachBatch
end
disable_ddl_transaction!
def up
add_column :merge_request_diffs, :commits_count, :integer
say 'Populating the MergeRequestDiff `commits_count`'
queue_background_migration_jobs_by_range_at_intervals(MergeRequestDiff, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
remove_column :merge_request_diffs, :commits_count
end
end
......@@ -31,6 +31,7 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
predicate = namespaces[:owner_id].eq(users[:id])
.and(namespaces[:type].eq(nil))
.and(users[:username].matches(path))
update_sql = if Gitlab::Database.postgresql?
"UPDATE users SET username = namespaces.path "\
"FROM namespaces WHERE #{predicate.to_sql}"
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveSoftRemovedObjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
module SoftRemoved
extend ActiveSupport::Concern
included do
scope :soft_removed, -> { where('deleted_at IS NOT NULL') }
end
end
class User < ActiveRecord::Base
self.table_name = 'users'
include EachBatch
end
class Issue < ActiveRecord::Base
self.table_name = 'issues'
include EachBatch
include SoftRemoved
end
class MergeRequest < ActiveRecord::Base
self.table_name = 'merge_requests'
include EachBatch
include SoftRemoved
end
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
include EachBatch
include SoftRemoved
scope :soft_removed_personal, -> { soft_removed.where(type: nil) }
scope :soft_removed_group, -> { soft_removed.where(type: 'Group') }
end
class Route < ActiveRecord::Base
self.table_name = 'routes'
include EachBatch
include SoftRemoved
end
class Project < ActiveRecord::Base
self.table_name = 'projects'
include EachBatch
include SoftRemoved
end
class CiPipelineSchedule < ActiveRecord::Base
self.table_name = 'ci_pipeline_schedules'
include EachBatch
include SoftRemoved
end
class CiTrigger < ActiveRecord::Base
self.table_name = 'ci_triggers'
include EachBatch
include SoftRemoved
end
MODELS = [Issue, MergeRequest, CiPipelineSchedule, CiTrigger].freeze
def up
disable_statement_timeout
remove_personal_routes
remove_personal_namespaces
remove_group_namespaces
remove_simple_soft_removed_rows
end
def down
# The data removed by this migration can't be restored in an automated way.
end
def remove_simple_soft_removed_rows
create_temporary_indexes
MODELS.each do |model|
say_with_time("Removing soft removed rows from #{model.table_name}") do
model.soft_removed.each_batch do |batch, index|
batch.delete_all
end
end
end
ensure
remove_temporary_indexes
end
def create_temporary_indexes
MODELS.each do |model|
index_name = temporary_index_name_for(model)
# Without this index the removal process can take a very long time. For
# example, getting the next ID of a batch for the `issues` table in
# staging would take between 15 and 20 seconds.
next if temporary_index_exists?(model)
say_with_time("Creating temporary index #{index_name}") do
add_concurrent_index(
model.table_name,
[:deleted_at, :id],
name: index_name,
where: 'deleted_at IS NOT NULL'
)
end
end
end
def remove_temporary_indexes
MODELS.each do |model|
index_name = temporary_index_name_for(model)
next unless temporary_index_exists?(model)
say_with_time("Removing temporary index #{index_name}") do
remove_concurrent_index_by_name(model.table_name, index_name)
end
end
end
def temporary_index_name_for(model)
"index_on_#{model.table_name}_tmp"
end
def temporary_index_exists?(model)
index_name = temporary_index_name_for(model)
index_exists?(model.table_name, [:deleted_at, :id], name: index_name)
end
def remove_personal_namespaces
# Some personal namespaces are left behind in case of GitLab.com. In these
# cases the associated data such as the projects and users has already been
# removed.
Namespace.soft_removed_personal.each_batch do |batch|
batch.delete_all
end
end
def remove_group_namespaces
admin_id = id_for_admin_user
unless admin_id
say 'Not scheduling soft removed groups for removal as no admin user ' \
'could be found. You will need to remove any such groups manually.'
return
end
# Left over groups can't be easily removed because we may also need to
# remove memberships, repositories, and other associated data. As a result
# we'll just schedule a Sidekiq job to remove these.
#
# As of January 5th, 2018 there are 36 groups that will be removed using
# this code.
Namespace.select(:id).soft_removed_group.each_batch(of: 10) do |batch, index|
batch.each do |ns|
schedule_group_removal(index * 5.minutes, ns.id, admin_id)
end
end
end
def schedule_group_removal(delay, group_id, user_id)
if migrate_inline?
GroupDestroyWorker.new.perform(group_id, user_id)
else
GroupDestroyWorker.perform_in(delay, group_id, user_id)
end
end
def remove_personal_routes
namespaces = Namespace.select(1)
.soft_removed
.where('namespaces.type IS NULL')
.where('routes.source_type = ?', 'Namespace')
.where('routes.source_id = namespaces.id')
Route.where('EXISTS (?)', namespaces).each_batch do |batch|
batch.delete_all
end
end
def id_for_admin_user
User.where(admin: true).limit(1).pluck(:id).first
end
def migrate_inline?
Rails.env.test? || Rails.env.development?
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveDeletedAtColumns < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
TABLES = %i[issues merge_requests namespaces ci_pipeline_schedules ci_triggers].freeze
COLUMN = :deleted_at
def up
TABLES.each do |table|
remove_column(table, COLUMN) if column_exists?(table, COLUMN)
end
end
def down
TABLES.each do |table|
unless column_exists?(table, COLUMN)
add_column(table, COLUMN, :datetime_with_timezone)
end
unless index_exists?(table, COLUMN)
add_concurrent_index(table, COLUMN)
end
end
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171230123729) do
ActiveRecord::Schema.define(version: 20180105212544) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -357,7 +357,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.integer "project_id"
t.integer "owner_id"
t.boolean "active", default: true
t.datetime "deleted_at"
t.datetime "created_at"
t.datetime "updated_at"
end
......@@ -467,7 +466,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
create_table "ci_triggers", force: :cascade do |t|
t.string "token"
t.datetime "deleted_at"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "project_id"
......@@ -861,7 +859,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.integer "iid"
t.integer "updated_by_id"
t.boolean "confidential", default: false, null: false
t.datetime "deleted_at"
t.date "due_date"
t.integer "moved_to_id"
t.integer "lock_version"
......@@ -878,7 +875,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree
......@@ -1044,6 +1040,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.string "real_size"
t.string "head_commit_sha"
t.string "start_commit_sha"
t.integer "commits_count"
end
add_index "merge_request_diffs", ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
......@@ -1087,7 +1084,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.boolean "merge_when_pipeline_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha"
t.integer "lock_version"
t.text "title_html"
......@@ -1106,7 +1102,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
......@@ -1166,7 +1161,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at"
t.text "description_html"
t.boolean "lfs_enabled"
t.integer "parent_id"
......@@ -1176,7 +1170,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do
end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree
add_index "namespaces", ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
......
......@@ -32,7 +32,9 @@ options:
## AWS Elastic File System
GitLab does not recommend using AWS Elastic File System (EFS).
GitLab strongly recommends against using AWS Elastic File System (EFS).
Our support team will not be able to assist on performance issues related to
file system access.
Customers and users have reported that AWS EFS does not perform well for GitLab's
use-case. There are several issues that can cause problems. For these reasons
......
......@@ -24,7 +24,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
......@@ -55,7 +54,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
......@@ -85,7 +83,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descri
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
......@@ -116,7 +113,6 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descrip
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
......@@ -146,7 +142,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
......
......@@ -2,7 +2,7 @@
> [Introduced][ce-6373] in GitLab 8.15.
### Snippet visibility level
## Snippet visibility level
Snippets in GitLab can be either private, internal, or public.
You can set it with the `visibility` field in the snippet.
......@@ -273,4 +273,5 @@ Example response:
}
```
[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
[ce-6373]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6373
[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12655
......@@ -17,7 +17,6 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Autoscaling GitLab Runners on AWS](runner_autoscale_aws/index.md) | Admin guide | 2017-11-24 |
| [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md) | Tutorial | 2017-08-31 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017-07-27 |
......@@ -62,7 +61,6 @@ upgrade, integrate, migrate to GitLab:
| :------------ | :------: | --------------: |
| [Video Tutorial: Idea to Production on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/) | Tutorial | 2017-01-23 |
| [How to Setup a GitLab Instance on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) | Tutorial | 2016-07-13 |
| [Get started with OpenShift Origin 3 and GitLab](openshift_and_gitlab/index.md) | Tutorial | 2016-06-28 |
| [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) | Tutorial | 2016-04-27 |
## Software development
......
This diff is collapsed.
......@@ -120,7 +120,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
- **Articles**
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](../articles/laravel_with_gitlab_and_envoy/index.md)
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](examples/laravel_with_gitlab_and_envoy/index.md)
- [How to deploy Maven projects to Artifactory with GitLab CI/CD](examples/artifactory_and_gitlab/index.md)
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
......
......@@ -16,6 +16,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Testing a PHP application](php.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
### Ruby
......
This diff is collapsed.
......@@ -27,7 +27,7 @@ the hardware requirements.
- [Installing in Kubernetes](kubernetes/index.md) - Install GitLab into a Kubernetes
Cluster using our official Helm Chart Repository.
- [Install GitLab on OpenShift](../articles/openshift_and_gitlab/index.md)
- [Install GitLab on OpenShift](openshift_and_gitlab/index.md)
- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/)
- [Install GitLab on Azure](azure/index.md)
- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md)
......
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.
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.
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