Commit 8a49e97b authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into projects-l

parents f7160499 685780d5
...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2' ...@@ -385,9 +385,6 @@ gem 'ruby-prof', '~> 0.16.2'
# OAuth # OAuth
gem 'oauth2', '~> 1.4' gem 'oauth2', '~> 1.4'
# Soft deletion
gem 'paranoia', '~> 2.3.1'
# Health check # Health check
gem 'health_check', '~> 2.6.0' gem 'health_check', '~> 2.6.0'
......
...@@ -580,8 +580,6 @@ GEM ...@@ -580,8 +580,6 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.12.0) parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.2) parser (2.4.0.2)
ast (~> 2.3) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
...@@ -1117,7 +1115,6 @@ DEPENDENCIES ...@@ -1117,7 +1115,6 @@ DEPENDENCIES
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.3.1)
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-host (~> 1.0.0) peek-host (~> 1.0.0)
......
...@@ -12,7 +12,6 @@ import notificationsDropdown from './notifications_dropdown'; ...@@ -12,7 +12,6 @@ import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar'; import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription'; import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter'; import LineHighlighter from './line_highlighter';
import groupsSelect from './groups_select';
import NewCommitForm from './new_commit_form'; import NewCommitForm from './new_commit_form';
import Project from './project'; import Project from './project';
import projectAvatar from './project_avatar'; import projectAvatar from './project_avatar';
...@@ -40,7 +39,6 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; ...@@ -40,7 +39,6 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import ShortcutsWiki from './shortcuts_wiki'; import ShortcutsWiki from './shortcuts_wiki';
import Pipelines from './pipelines';
import BlobViewer from './blob/viewer/index'; import BlobViewer from './blob/viewer/index';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
...@@ -49,11 +47,9 @@ import GfmAutoComplete from './gfm_auto_complete'; ...@@ -49,11 +47,9 @@ import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob'; import ShortcutsBlob from './shortcuts_blob';
import Star from './star'; import Star from './star';
import TreeView from './tree'; import TreeView from './tree';
import VersionCheckImage from './version_check_image';
import Wikis from './wikis'; import Wikis from './wikis';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels'; import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import PerformanceBar from './performance_bar'; import PerformanceBar from './performance_bar';
import initNotes from './init_notes'; import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters'; import initLegacyFilters from './init_legacy_filters';
...@@ -145,9 +141,6 @@ import Activities from './activities'; ...@@ -145,9 +141,6 @@ import Activities from './activities';
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
switch (page) { switch (page) {
case 'profiles:preferences:show':
initExperimentalFlags();
break;
case 'sessions:new': case 'sessions:new':
import('./pages/sessions/new') import('./pages/sessions/new')
.then(callDefault) .then(callDefault)
...@@ -390,23 +383,16 @@ import Activities from './activities'; ...@@ -390,23 +383,16 @@ import Activities from './activities';
break; break;
case 'projects:pipelines:new': case 'projects:pipelines:new':
case 'projects:pipelines:create': case 'projects:pipelines:create':
new NewBranchForm($('.js-new-pipeline-form')); import('./pages/projects/pipelines/new')
.then(callDefault)
.catch(fail);
break; break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; import('./pages/projects/pipelines/builds')
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`; .then(callDefault)
.catch(fail);
new Pipelines({
initTabs: true,
pipelineStatusUrl,
tabsOptions: {
action: controllerAction,
defaultAction: 'pipelines',
parentEl: '.pipelines-tabs',
},
});
break; break;
case 'groups:activity': case 'groups:activity':
new Activities(); new Activities();
...@@ -428,11 +414,9 @@ import Activities from './activities'; ...@@ -428,11 +414,9 @@ import Activities from './activities';
new UsersSelect(); new UsersSelect();
break; break;
case 'projects:project_members:index': case 'projects:project_members:index':
memberExpirationDate('.js-access-expiration-date-groups'); import('./pages/projects/project_members/')
groupsSelect(); .then(callDefault)
memberExpirationDate(); .catch(fail);
new Members();
new UsersSelect();
break; break;
case 'groups:new': case 'groups:new':
case 'groups:create': case 'groups:create':
...@@ -536,7 +520,9 @@ import Activities from './activities'; ...@@ -536,7 +520,9 @@ import Activities from './activities';
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'help:index': case 'help:index':
VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); import('./pages/help')
.then(callDefault)
.catch(fail);
break; break;
case 'search:show': case 'search:show':
import('./pages/search/show') import('./pages/search/show')
...@@ -579,14 +565,16 @@ import Activities from './activities'; ...@@ -579,14 +565,16 @@ import Activities from './activities';
case 'import:fogbugz:new_user_map': case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break; break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
.then(callDefault)
.catch(fail);
break;
case 'admin:impersonation_tokens:index': case 'admin:impersonation_tokens:index':
import('./pages/admin/impersonation_tokens') import('./pages/admin/impersonation_tokens')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'profiles:personal_access_tokens:index':
new DueDateSelectors();
break;
case 'projects:clusters:show': case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
.then(cluster => new cluster.default()) // eslint-disable-line new-cap .then(cluster => new cluster.default()) // eslint-disable-line new-cap
...@@ -672,8 +660,9 @@ import Activities from './activities'; ...@@ -672,8 +660,9 @@ import Activities from './activities';
new UserCallout(); new UserCallout();
break; break;
case 'profiles': case 'profiles':
new NotificationsForm(); import('./pages/profiles/index/')
notificationsDropdown(); .then(callDefault)
.catch(fail);
break; break;
case 'projects': case 'projects':
new Project(); 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> <script>
import tooltip from '../directives/tooltip';
/** /**
* Falls back to the code used in `copy_to_clipboard.js` * Falls back to the code used in `copy_to_clipboard.js`
*/ */
export default { export default {
name: 'ClipboardButton', name: 'ClipboardButton',
directives: {
tooltip,
},
props: { props: {
text: { text: {
type: String, type: String,
...@@ -14,6 +18,16 @@ ...@@ -14,6 +18,16 @@
type: String, type: String,
required: true, required: true,
}, },
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
tooltipContainer: {
type: [String, Boolean],
required: false,
default: false,
},
}, },
}; };
</script> </script>
...@@ -22,8 +36,11 @@ ...@@ -22,8 +36,11 @@
<button <button
type="button" type="button"
class="btn btn-transparent btn-clipboard" class="btn btn-transparent btn-clipboard"
:data-title="title" :title="title"
:data-clipboard-text="text" :data-clipboard-text="text"
v-tooltip
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
> >
<i <i
aria-hidden="true" aria-hidden="true"
......
...@@ -65,6 +65,7 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -65,6 +65,7 @@ class Admin::RunnersController < Admin::ApplicationController
else else
Project.all Project.all
end end
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any? @projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
end end
......
...@@ -8,6 +8,7 @@ module GroupTree ...@@ -8,6 +8,7 @@ module GroupTree
# Only show root groups if no parent-id is given # Only show root groups if no parent-id is given
groups.where(parent_id: params[:parent_id]) groups.where(parent_id: params[:parent_id])
end end
@groups = @groups.with_selects_for_list(archived: params[:archived]) @groups = @groups.with_selects_for_list(archived: params[:archived])
.sort(@sort = params[:sort]) .sort(@sort = params[:sort])
.page(params[:page]) .page(params[:page])
......
...@@ -32,6 +32,7 @@ module RoutableActions ...@@ -32,6 +32,7 @@ module RoutableActions
if canonical_path.casecmp(requested_full_path) != 0 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." 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 end
redirect_to build_canonical_path(routable) redirect_to build_canonical_path(routable)
end end
end end
......
...@@ -12,6 +12,7 @@ class MetricsController < ActionController::Base ...@@ -12,6 +12,7 @@ class MetricsController < ActionController::Base
) )
"# Metrics are disabled, see: #{help_page}\n" "# Metrics are disabled, see: #{help_page}\n"
end end
render text: response, content_type: 'text/plain; version=0.0.4' render text: response, content_type: 'text/plain; version=0.0.4'
end end
......
...@@ -83,6 +83,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -83,6 +83,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if ticket if ticket
handle_service_ticket oauth['provider'], ticket handle_service_ticket oauth['provider'], ticket
end end
handle_omniauth handle_omniauth
end end
...@@ -90,6 +91,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -90,6 +91,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if params['sid'] if params['sid']
handle_service_ticket oauth['provider'], params['sid'] handle_service_ticket oauth['provider'], params['sid']
end end
handle_omniauth handle_omniauth
end end
...@@ -124,6 +126,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -124,6 +126,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login. # Only allow properly saved users to login.
if @user.persisted? && @user.valid? if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider']) log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled? if @user.two_factor_enabled?
params[:remember_me] = '1' if remember_me? params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user) prompt_for_two_factor(@user)
......
...@@ -150,6 +150,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -150,6 +150,7 @@ class Projects::BlobController < Projects::ApplicationController
if params[:file].present? if params[:file].present?
params[:file_name] = params[:file].original_filename params[:file_name] = params[:file].original_filename
end end
File.join(@path, params[:file_name]) File.join(@path, params[:file_name])
elsif params[:file_path].present? elsif params[:file_path].present?
params[:file_path] params[:file_path]
......
...@@ -27,6 +27,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -27,6 +27,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
unless @key.valid? && @project.deploy_keys << @key unless @key.valid? && @project.deploy_keys << @key
flash[:alert] = @key.errors.full_messages.join(', ').html_safe flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end end
redirect_to_repository_settings(@project) redirect_to_repository_settings(@project)
end end
......
...@@ -21,6 +21,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -21,6 +21,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?) @hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe flash[:alert] = @hook.errors.full_messages.join.html_safe
end end
redirect_to project_settings_integrations_path(@project) redirect_to project_settings_integrations_path(@project)
end end
......
...@@ -48,6 +48,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -48,6 +48,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
else else
[] []
end end
@diff_notes_disabled = true @diff_notes_disabled = true
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user).last
......
...@@ -203,6 +203,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -203,6 +203,7 @@ class ProjectsController < Projects::ApplicationController
else else
flash[:alert] = _("Project export could not be deleted.") flash[:alert] = _("Project export could not be deleted.")
end end
redirect_to(edit_project_path(@project)) redirect_to(edit_project_path(@project))
end end
......
...@@ -28,6 +28,7 @@ class SessionsController < Devise::SessionsController ...@@ -28,6 +28,7 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil, resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil) reset_password_sent_at: nil)
end end
# hide the signed-in notification # hide the signed-in notification
flash[:notice] = nil flash[:notice] = nil
log_audit_event(current_user, resource, with: authentication_method) log_audit_event(current_user, resource, with: authentication_method)
......
...@@ -63,6 +63,7 @@ class GroupDescendantsFinder ...@@ -63,6 +63,7 @@ class GroupDescendantsFinder
groups_table = Group.arel_table groups_table = Group.arel_table
visible_to_user = groups_table[:visibility_level] visible_to_user = groups_table[:visibility_level]
.in(Gitlab::VisibilityLevel.levels_for_user(current_user)) .in(Gitlab::VisibilityLevel.levels_for_user(current_user))
if current_user if current_user
authorized_groups = GroupsFinder.new(current_user, authorized_groups = GroupsFinder.new(current_user,
all_available: false) all_available: false)
...@@ -115,6 +116,7 @@ class GroupDescendantsFinder ...@@ -115,6 +116,7 @@ class GroupDescendantsFinder
else else
direct_child_groups direct_child_groups
end end
groups.with_selects_for_list(archived: params[:archived]).order_by(sort) groups.with_selects_for_list(archived: params[:archived]).order_by(sort)
end end
...@@ -140,6 +142,7 @@ class GroupDescendantsFinder ...@@ -140,6 +142,7 @@ class GroupDescendantsFinder
else else
direct_child_projects direct_child_projects
end end
projects.with_route.order_by(sort) projects.with_route.order_by(sort)
end end
......
...@@ -34,6 +34,7 @@ class GroupProjectsFinder < ProjectsFinder ...@@ -34,6 +34,7 @@ class GroupProjectsFinder < ProjectsFinder
else else
collection_without_user collection_without_user
end end
union(projects) union(projects)
end end
......
...@@ -203,6 +203,7 @@ module MarkupHelper ...@@ -203,6 +203,7 @@ module MarkupHelper
node.content = node.content.truncate(num_remaining) node.content = node.content.truncate(num_remaining)
truncated = true truncated = true
end end
content_length += node.content.length content_length += node.content.length
end end
......
...@@ -12,6 +12,7 @@ module NavHelper ...@@ -12,6 +12,7 @@ module NavHelper
current_path?('projects/merge_requests/conflicts#show') || current_path?('projects/merge_requests/conflicts#show') ||
current_path?('issues#show') || current_path?('issues#show') ||
current_path?('milestones#show') current_path?('milestones#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
%w[page-gutter right-sidebar-collapsed] %w[page-gutter right-sidebar-collapsed]
else else
......
...@@ -89,6 +89,7 @@ module SnippetsHelper ...@@ -89,6 +89,7 @@ module SnippetsHelper
snippet_chunk = [lined_content[line_number]] snippet_chunk = [lined_content[line_number]]
snippet_start_line = line_number snippet_start_line = line_number
end end
last_line = line_number last_line = line_number
end end
# Add final chunk to chunk array # Add final chunk to chunk array
......
...@@ -58,6 +58,7 @@ module SubmoduleHelper ...@@ -58,6 +58,7 @@ module SubmoduleHelper
url_no_dotgit = url.chomp('.git') url_no_dotgit = url.chomp('.git')
return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/', return true if url_no_dotgit == [Gitlab.config.gitlab.url, '/', namespace, '/',
project].join('') project].join('')
url_with_dotgit = url_no_dotgit + '.git' url_with_dotgit = url_no_dotgit + '.git'
url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join('')) url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
end end
......
...@@ -30,6 +30,7 @@ module TodosHelper ...@@ -30,6 +30,7 @@ module TodosHelper
else else
todo.target_reference todo.target_reference
end end
link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end end
......
...@@ -418,6 +418,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -418,6 +418,7 @@ class ApplicationSetting < ActiveRecord::Base
super(group_full_path) super(group_full_path)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end end
return return
end end
......
...@@ -2,8 +2,9 @@ module Ci ...@@ -2,8 +2,9 @@ module Ci
class PipelineSchedule < ActiveRecord::Base class PipelineSchedule < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include Importable include Importable
include IgnorableColumn
acts_as_paranoid ignore_column :deleted_at
belongs_to :project belongs_to :project
belongs_to :owner, class_name: 'User' belongs_to :owner, class_name: 'User'
......
module Ci module Ci
class Trigger < ActiveRecord::Base class Trigger < ActiveRecord::Base
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include IgnorableColumn
acts_as_paranoid ignore_column :deleted_at
belongs_to :project belongs_to :project
belongs_to :owner, class_name: "User" belongs_to :owner, class_name: "User"
......
...@@ -10,7 +10,6 @@ module InternalId ...@@ -10,7 +10,6 @@ module InternalId
if iid.blank? if iid.blank?
parent = project || group parent = project || group
records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid) max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1 self.iid = max_iid.to_i + 1
......
...@@ -314,6 +314,7 @@ module Issuable ...@@ -314,6 +314,7 @@ module Issuable
includes = [] includes = []
includes << :author unless notes.authors_loaded? includes << :author unless notes.authors_loaded?
includes << :award_emoji unless notes.award_emojis_loaded? includes << :award_emoji unless notes.award_emojis_loaded?
if includes.any? if includes.any?
notes.includes(includes) notes.includes(includes)
else else
......
...@@ -25,6 +25,7 @@ module LoadedInGroupList ...@@ -25,6 +25,7 @@ module LoadedInGroupList
base_count = projects.project(Arel.star.count.as('preloaded_project_count')) base_count = projects.project(Arel.star.count.as('preloaded_project_count'))
.where(projects[:namespace_id].eq(namespaces[:id])) .where(projects[:namespace_id].eq(namespaces[:id]))
if archived == 'only' if archived == 'only'
base_count.where(projects[:archived].eq(true)) base_count.where(projects[:archived].eq(true))
elsif Gitlab::Utils.to_boolean(archived) elsif Gitlab::Utils.to_boolean(archived)
......
...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base ...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base
include ThrottledTouch include ThrottledTouch
include IgnorableColumn include IgnorableColumn
ignore_column :assignee_id, :branch_name ignore_column :assignee_id, :branch_name, :deleted_at
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
...@@ -78,8 +78,6 @@ class Issue < ActiveRecord::Base ...@@ -78,8 +78,6 @@ class Issue < ActiveRecord::Base
end end
end end
acts_as_paranoid
class << self class << self
alias_method :in_parents, :in_projects alias_method :in_parents, :in_projects
end end
......
...@@ -132,6 +132,7 @@ class Label < ActiveRecord::Base ...@@ -132,6 +132,7 @@ class Label < ActiveRecord::Base
else else
priorities.find_by(project: project) priorities.find_by(project: project)
end end
priority.try(:priority) priority.try(:priority)
end end
......
...@@ -11,7 +11,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -11,7 +11,8 @@ class MergeRequest < ActiveRecord::Base
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched :ref_fetched,
:deleted_at
belongs_to :target_project, class_name: "Project" belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project" belongs_to :source_project, class_name: "Project"
...@@ -150,8 +151,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -150,8 +151,6 @@ class MergeRequest < ActiveRecord::Base
after_save :keep_around_commit after_save :keep_around_commit
acts_as_paranoid
def self.reference_prefix def self.reference_prefix
'!' '!'
end end
...@@ -794,6 +793,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -794,6 +793,7 @@ class MergeRequest < ActiveRecord::Base
if !include_description && closes_issues_references.present? if !include_description && closes_issues_references.present?
message << "Closes #{closes_issues_references.to_sentence}" message << "Closes #{closes_issues_references.to_sentence}"
end end
message << "#{description}" if include_description && description.present? message << "#{description}" if include_description && description.present?
message << "See merge request #{to_reference(full: true)}" message << "See merge request #{to_reference(full: true)}"
......
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
acts_as_paranoid without_default_scope: true
include CacheMarkdownField include CacheMarkdownField
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
...@@ -10,6 +8,9 @@ class Namespace < ActiveRecord::Base ...@@ -10,6 +8,9 @@ class Namespace < ActiveRecord::Base
include AfterCommitQueue include AfterCommitQueue
include Storage::LegacyNamespace include Storage::LegacyNamespace
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include IgnorableColumn
ignore_column :deleted_at
# Prevent users from creating unreasonably deep level of nesting. # Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of # The number 20 was taken based on maximum nesting level of
...@@ -221,12 +222,6 @@ class Namespace < ActiveRecord::Base ...@@ -221,12 +222,6 @@ class Namespace < ActiveRecord::Base
has_parent? has_parent?
end 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 private
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
......
...@@ -224,6 +224,7 @@ module Network ...@@ -224,6 +224,7 @@ module Network
space_base = parents.first.space space_base = parents.first.space
end end
end end
space_base space_base
end end
......
...@@ -9,6 +9,7 @@ class NotificationRecipient ...@@ -9,6 +9,7 @@ class NotificationRecipient
group: nil, group: nil,
skip_read_ability: false skip_read_ability: false
) )
unless NotificationSetting.levels.key?(type) || type == :subscription unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}" raise ArgumentError, "invalid type: #{type.inspect}"
end end
......
...@@ -633,6 +633,7 @@ class Project < ActiveRecord::Base ...@@ -633,6 +633,7 @@ class Project < ActiveRecord::Base
project_import_data.data ||= {} project_import_data.data ||= {}
project_import_data.data = project_import_data.data.merge(data) project_import_data.data = project_import_data.data.merge(data)
end end
if credentials if credentials
project_import_data.credentials ||= {} project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials) project_import_data.credentials = project_import_data.credentials.merge(credentials)
......
...@@ -110,6 +110,7 @@ class HipchatService < Service ...@@ -110,6 +110,7 @@ class HipchatService < Service
message = "" message = ""
message << "#{push[:user_name]} " message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before) if Gitlab::Git.blank_ref?(before)
message << "pushed new #{ref_type} <a href=\""\ message << "pushed new #{ref_type} <a href=\""\
"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\ "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
......
...@@ -1014,6 +1014,7 @@ class Repository ...@@ -1014,6 +1014,7 @@ class Repository
else else
cache.fetch(key, &block) cache.fetch(key, &block)
end end
instance_variable_set(ivar, value) instance_variable_set(ivar, value)
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
# Even if the above `#exists?` check passes these errors might still # Even if the above `#exists?` check passes these errors might still
......
...@@ -250,6 +250,7 @@ class Service < ActiveRecord::Base ...@@ -250,6 +250,7 @@ class Service < ActiveRecord::Base
teamcity teamcity
microsoft_teams microsoft_teams
] ]
if Rails.env.development? if Rails.env.development?
service_names += %w[mock_ci mock_deployment mock_monitoring] service_names += %w[mock_ci mock_deployment mock_monitoring]
end end
......
...@@ -6,7 +6,6 @@ class IssueEntity < IssuableEntity ...@@ -6,7 +6,6 @@ class IssueEntity < IssuableEntity
expose :updated_by_id expose :updated_by_id
expose :created_at expose :created_at
expose :updated_at expose :updated_at
expose :deleted_at
expose :milestone, using: API::Entities::Milestone expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity expose :labels, using: LabelEntity
expose :lock_version expose :lock_version
......
...@@ -16,6 +16,7 @@ class CreateDeploymentService ...@@ -16,6 +16,7 @@ class CreateDeploymentService
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
environment.external_url = expanded_environment_url if environment.external_url = expanded_environment_url if
expanded_environment_url expanded_environment_url
environment.fire_state_event(action) environment.fire_state_event(action)
return unless environment.save return unless environment.save
......
module Groups module Groups
class DestroyService < Groups::BaseService class DestroyService < Groups::BaseService
def async_execute def async_execute
group.soft_delete_without_removing_associations
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) 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}") Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end end
...@@ -23,7 +22,7 @@ module Groups ...@@ -23,7 +22,7 @@ module Groups
group.chat_team&.remove_mattermost_team(current_user) group.chat_team&.remove_mattermost_team(current_user)
group.really_destroy! group.destroy
end end
end end
end end
...@@ -53,7 +53,7 @@ module Users ...@@ -53,7 +53,7 @@ module Users
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
user_data = user.destroy user_data = user.destroy
namespace.really_destroy! namespace.destroy
user_data user_data
end end
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.limit-container-width{ class: container_class } .limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar .avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile') = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title %h1.project-title.qa-project-name
= @project.name = @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false) = visibility_level_icon(@project.visibility_level, fw: false)
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
.input-group-addon .input-group-addon
= root_url = 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 - else
.input-group-addon.static-namespace .input-group-addon.static-namespace
......
...@@ -30,12 +30,13 @@ ...@@ -30,12 +30,13 @@
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
#{ _('New file') } #{ _('New file') }
%li - unless @project.empty_repo?
= link_to new_project_branch_path(@project) do %li
#{ _('New branch') } = link_to new_project_branch_path(@project) do
%li #{ _('New branch') }
= link_to new_project_tag_path(@project) do %li
#{ _('New tag') } = link_to new_project_tag_path(@project) do
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project) - elsif current_user && current_user.already_forked?(@project)
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
Edit Edit
- if @project.group - 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 Promote
- if @milestone.active? - if @milestone.active?
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%span %span
= enabled_project_button(project, enabled_protocol) = enabled_project_button(project, enabled_protocol)
- else - 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 %span
= default_clone_protocol.upcase = default_clone_protocol.upcase
= icon('caret-down') = icon('caret-down')
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading') = icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - 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 %span.sr-only Promote to Group
= icon('level-up') = icon('level-up')
- if can?(current_user, :admin_label, label) - if can?(current_user, :admin_label, label)
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
\ \
- if @project.group - 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 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" = 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 ...@@ -4,7 +4,7 @@ class GroupDestroyWorker
def perform(group_id, user_id) def perform(group_id, user_id)
begin begin
group = Group.with_deleted.find(group_id) group = Group.find(group_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
return return
end end
......
...@@ -13,6 +13,7 @@ class PagesWorker ...@@ -13,6 +13,7 @@ class PagesWorker
if result[:status] == :success if result[:status] == :success
result = Projects::UpdatePagesConfigurationService.new(build.project).execute result = Projects::UpdatePagesConfigurationService.new(build.project).execute
end end
result result
end end
......
---
title: Adds Rubocop rule for line break around conditionals
merge_request: 15739
author: Jacopo Beschi @jacopo-beschi
type: added
---
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 ...@@ -61,6 +61,7 @@ module Gitlab
# - Any parameter containing `secret` # - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt) # - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url) # - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
# - Build variables (:variables) # - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
...@@ -75,6 +76,7 @@ module Gitlab ...@@ -75,6 +76,7 @@ module Gitlab
key key
otp_attempt otp_attempt
sentry_dsn sentry_dsn
trace
variables variables
) )
...@@ -149,6 +151,7 @@ module Gitlab ...@@ -149,6 +151,7 @@ module Gitlab
caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5 caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
caching_config_hash[:pool_timeout] = 1 caching_config_hash[:pool_timeout] = 1
end end
config.cache_store = :redis_store, caching_config_hash config.cache_store = :redis_store, caching_config_hash
config.active_record.raise_in_transactional_callbacks = true config.active_record.raise_in_transactional_callbacks = true
......
...@@ -68,6 +68,7 @@ class Settings < Settingslogic ...@@ -68,6 +68,7 @@ class Settings < Settingslogic
end end
values.delete_if { |value| value.nil? } values.delete_if { |value| value.nil? }
end end
values values
end end
...@@ -78,6 +79,7 @@ class Settings < Settingslogic ...@@ -78,6 +79,7 @@ class Settings < Settingslogic
if current.is_a? String if current.is_a? String
value = modul.const_get(current.upcase) rescue default value = modul.const_get(current.upcase) rescue default
end end
value value
end end
......
...@@ -241,6 +241,7 @@ Devise.setup do |config| ...@@ -241,6 +241,7 @@ Devise.setup do |config|
true true
end end
end end
if provider['name'] == 'authentiq' if provider['name'] == 'authentiq'
provider['args'][:remote_sign_out_handler] = lambda do |request| provider['args'][:remote_sign_out_handler] = lambda do |request|
authentiq_session = request.params['sid'] authentiq_session = request.params['sid']
......
...@@ -2,6 +2,7 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Re ...@@ -2,6 +2,7 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Re
Peek.into Peek::Views::Host Peek.into Peek::Views::Host
Peek.into Peek::Views::PerformanceBar Peek.into Peek::Views::PerformanceBar
if Gitlab::Database.mysql? if Gitlab::Database.mysql?
require 'peek-mysql2' require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client PEEK_DB_CLIENT = ::Mysql2::Client
...@@ -11,6 +12,7 @@ else ...@@ -11,6 +12,7 @@ else
PEEK_DB_CLIENT = ::PG::Connection PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG PEEK_DB_VIEW = Peek::Views::PG
end end
Peek.into PEEK_DB_VIEW Peek.into PEEK_DB_VIEW
Peek.into Peek::Views::Redis Peek.into Peek::Views::Redis
Peek.into Peek::Views::Sidekiq Peek.into Peek::Views::Sidekiq
......
...@@ -23,6 +23,7 @@ class CreateForkNetworks < ActiveRecord::Migration ...@@ -23,6 +23,7 @@ class CreateForkNetworks < ActiveRecord::Migration
if foreign_keys_for(:fork_networks, :root_project_id).any? if foreign_keys_for(:fork_networks, :root_project_id).any?
remove_foreign_key :fork_networks, column: :root_project_id remove_foreign_key :fork_networks, column: :root_project_id
end end
drop_table :fork_networks drop_table :fork_networks
end end
end end
...@@ -21,6 +21,7 @@ class CreateForkNetworkMembers < ActiveRecord::Migration ...@@ -21,6 +21,7 @@ class CreateForkNetworkMembers < ActiveRecord::Migration
if foreign_keys_for(:fork_network_members, :forked_from_project_id).any? if foreign_keys_for(:fork_network_members, :forked_from_project_id).any?
remove_foreign_key :fork_network_members, column: :forked_from_project_id remove_foreign_key :fork_network_members, column: :forked_from_project_id
end end
drop_table :fork_network_members drop_table :fork_network_members
end end
end end
...@@ -9,6 +9,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration ...@@ -9,6 +9,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
return unless Gitlab::Database.postgresql? return unless Gitlab::Database.postgresql?
disable_statement_timeout disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.5 if Gitlab::Database.version.to_f >= 9.5
# Allow us to hot-patch the index manually ahead of the migration # 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));" execute "CREATE INDEX CONCURRENTLY IF NOT EXISTS #{INDEX_NAME} ON namespaces (lower(name));"
...@@ -21,6 +22,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration ...@@ -21,6 +22,7 @@ class AddIndexOnNamespacesLowerName < ActiveRecord::Migration
return unless Gitlab::Database.postgresql? return unless Gitlab::Database.postgresql?
disable_statement_timeout disable_statement_timeout
if Gitlab::Database.version.to_f >= 9.2 if Gitlab::Database.version.to_f >= 9.2
execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};" execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
else else
......
...@@ -31,6 +31,7 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration ...@@ -31,6 +31,7 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
predicate = namespaces[:owner_id].eq(users[:id]) predicate = namespaces[:owner_id].eq(users[:id])
.and(namespaces[:type].eq(nil)) .and(namespaces[:type].eq(nil))
.and(users[:username].matches(path)) .and(users[:username].matches(path))
update_sql = if Gitlab::Database.postgresql? update_sql = if Gitlab::Database.postgresql?
"UPDATE users SET username = namespaces.path "\ "UPDATE users SET username = namespaces.path "\
"FROM namespaces WHERE #{predicate.to_sql}" "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
...@@ -357,7 +357,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -357,7 +357,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.integer "project_id" t.integer "project_id"
t.integer "owner_id" t.integer "owner_id"
t.boolean "active", default: true t.boolean "active", default: true
t.datetime "deleted_at"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
end end
...@@ -467,7 +466,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -467,7 +466,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
create_table "ci_triggers", force: :cascade do |t| create_table "ci_triggers", force: :cascade do |t|
t.string "token" t.string "token"
t.datetime "deleted_at"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "project_id" t.integer "project_id"
...@@ -861,7 +859,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -861,7 +859,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.integer "iid" t.integer "iid"
t.integer "updated_by_id" t.integer "updated_by_id"
t.boolean "confidential", default: false, null: false t.boolean "confidential", default: false, null: false
t.datetime "deleted_at"
t.date "due_date" t.date "due_date"
t.integer "moved_to_id" t.integer "moved_to_id"
t.integer "lock_version" t.integer "lock_version"
...@@ -878,7 +875,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -878,7 +875,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree 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", ["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", ["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", ["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 add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree
...@@ -1088,7 +1084,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -1088,7 +1084,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.boolean "merge_when_pipeline_succeeds", default: false, null: false t.boolean "merge_when_pipeline_succeeds", default: false, null: false
t.integer "merge_user_id" t.integer "merge_user_id"
t.string "merge_commit_sha" t.string "merge_commit_sha"
t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha" t.string "in_progress_merge_commit_sha"
t.integer "lock_version" t.integer "lock_version"
t.text "title_html" t.text "title_html"
...@@ -1107,7 +1102,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -1107,7 +1102,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree 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", ["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", ["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", ["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", ["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 add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
...@@ -1167,7 +1161,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -1167,7 +1161,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.boolean "share_with_group_lock", default: false t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at"
t.text "description_html" t.text "description_html"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.integer "parent_id" t.integer "parent_id"
...@@ -1177,7 +1170,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do ...@@ -1177,7 +1170,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
end end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree 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", "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", ["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 add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
......
...@@ -32,7 +32,9 @@ options: ...@@ -32,7 +32,9 @@ options:
## AWS Elastic File System ## 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 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 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/ ...@@ -24,7 +24,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/
"id": 10, "id": 10,
"description": "my trigger", "description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z", "created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null, "last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7", "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z", "updated_at": "2016-01-07T09:53:58.235Z",
...@@ -55,7 +54,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ ...@@ -55,7 +54,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/
"id": 10, "id": 10,
"description": "my trigger", "description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z", "created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null, "last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7", "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z", "updated_at": "2016-01-07T09:53:58.235Z",
...@@ -85,7 +83,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descri ...@@ -85,7 +83,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descri
"id": 10, "id": 10,
"description": "my trigger", "description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z", "created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null, "last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7", "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z", "updated_at": "2016-01-07T09:53:58.235Z",
...@@ -116,7 +113,6 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descrip ...@@ -116,7 +113,6 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descrip
"id": 10, "id": 10,
"description": "my trigger", "description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z", "created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null, "last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7", "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z", "updated_at": "2016-01-07T09:53:58.235Z",
...@@ -146,7 +142,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl ...@@ -146,7 +142,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl
"id": 10, "id": 10,
"description": "my trigger", "description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z", "created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null, "last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7", "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z", "updated_at": "2016-01-07T09:53:58.235Z",
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
> [Introduced][ce-6373] in GitLab 8.15. > [Introduced][ce-6373] in GitLab 8.15.
### Snippet visibility level ## Snippet visibility level
Snippets in GitLab can be either private, internal, or public. Snippets in GitLab can be either private, internal, or public.
You can set it with the `visibility` field in the snippet. You can set it with the `visibility` field in the snippet.
...@@ -273,4 +273,5 @@ Example response: ...@@ -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 ...@@ -17,7 +17,6 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date | | Article title | Category | Publishing date |
| :------------ | :------: | --------------: | | :------------ | :------: | --------------: |
| [Autoscaling GitLab Runners on AWS](runner_autoscale_aws/index.md) | Admin guide | 2017-11-24 | | [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 | | [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 | | [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 | | [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: ...@@ -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 | | [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 | | [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 | | [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 ## Software development
......
This diff is collapsed.
...@@ -120,7 +120,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline. ...@@ -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) - [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) - [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
- **Articles** - **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) - [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/) - [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/) - [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 ...@@ -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) - [Testing a PHP application](php.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.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 ### Ruby
......
This diff is collapsed.
...@@ -27,7 +27,7 @@ the hardware requirements. ...@@ -27,7 +27,7 @@ the hardware requirements.
- [Installing in Kubernetes](kubernetes/index.md) - Install GitLab into a Kubernetes - [Installing in Kubernetes](kubernetes/index.md) - Install GitLab into a Kubernetes
Cluster using our official Helm Chart Repository. 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 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 Azure](azure/index.md)
- [Install GitLab on Google Cloud Platform](google_cloud_platform/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