Commit e7d9291f authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2018-03-06' into 'master'

CE upstream - 2018-03-06 00:34 UTC

Closes gitaly#1054 et gitaly#1048

See merge request gitlab-org/gitlab-ee!4848
parents 5b10cf32 3984a4cc
......@@ -197,6 +197,17 @@ release. There are two levels of priority labels:
milestone. If these issues are not done in the current release, they will
strongly be considered for the next release.
### Severity labels (~S1, ~S2, etc.)
Severity labels help us clearly communicate the impact of a ~bug on users.
| Label | Meaning | Example |
|-------|------------------------------------------|---------|
| ~S1 | Feature broken, no workaround | Unable to create an issue |
| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line |
| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue |
| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed |
### Label for community contributors (~"Accepting Merge Requests")
Issues that are beneficial to our users, 'nice to haves', that we currently do
......
......@@ -426,7 +426,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly'
# Explicitly lock grpc as we know 1.9 is bad
# 1.10 is still being tested. See gitlab-org/gitaly#1059
gem 'grpc', '~> 1.8.3'
......
......@@ -309,7 +309,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.87.0)
gitaly-proto (0.88.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
......@@ -630,7 +630,7 @@ GEM
atomic (>= 1.0.0)
mysql2
peek
peek-performance_bar (1.3.0)
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
......@@ -1091,7 +1091,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.87.0)
gitaly-proto (~> 0.88.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
......@@ -216,6 +216,9 @@ export default class MilestoneSelect {
$value.html(milestoneLinkNoneTemplate);
return $sidebarCollapsedValue.find('span').text('No');
}
})
.catch(() => {
$loading.fadeOut();
});
}
}
......
......@@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@impersonation_token = finder.build(impersonation_token_params)
if @impersonation_token.save
flash[:impersonation_token] = @impersonation_token.token
redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
else
set_index_vars
......
......@@ -62,7 +62,7 @@ class InvitesController < ApplicationController
case source
when Project
project = member.source
label = "project #{project.name_with_namespace}"
label = "project #{project.full_name}"
path = project_path(project)
when Group
group = member.source
......
......@@ -38,7 +38,7 @@ class Projects::BlobController < Projects::ApplicationController
end
format.json do
page_title @blob.path, @ref, @project.name_with_namespace
page_title @blob.path, @ref, @project.full_name
show_json
end
......
......@@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController
def show
apply_diff_view_cookie!
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
render
end
def diff_for_path
......
......@@ -36,7 +36,7 @@ class Projects::TreeController < Projects::ApplicationController
end
format.json do
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
page_title @path.presence || _("Files"), @ref, @project.full_name
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
......
......@@ -132,7 +132,7 @@ class ProjectsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).async_execute
flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.name_with_namespace }
flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
redirect_to dashboard_projects_path, status: 302
rescue Projects::DestroyService::DestroyError => ex
......
......@@ -19,6 +19,10 @@
# non_archived: boolean
# iids: integer[]
# my_reaction_emoji: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
......@@ -79,6 +83,7 @@ class IssuableFinder
def filter_items(items)
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
......@@ -283,6 +288,13 @@ class IssuableFinder
end
end
def by_updated_at(items)
items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
items
end
def by_state(items)
case params[:state].to_s
when 'closed'
......
......@@ -17,6 +17,10 @@
# my_reaction_emoji: string
# public_only: boolean
# due_date: date or '0', '', 'overdue', 'week', or 'month'
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class IssuesFinder < IssuableFinder
prepend EE::IssuesFinder
......
......@@ -19,6 +19,10 @@
# my_reaction_emoji: string
# source_branch: string
# target_branch: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class MergeRequestsFinder < IssuableFinder
def klass
......
......@@ -8,10 +8,10 @@ module ImportHelper
"#{namespace}/#{name}"
end
def provider_project_link(provider, path_with_namespace)
url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
def provider_project_link(provider, full_path)
url = __send__("#{provider}_project_url", full_path) # rubocop:disable GitlabSecurity/PublicSend
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
link_to full_path, url, target: '_blank', rel: 'noopener noreferrer'
end
def import_will_timeout_message(_ci_cd_only)
......@@ -38,8 +38,8 @@ module ImportHelper
private
def github_project_url(path_with_namespace)
"#{github_root_url}/#{path_with_namespace}"
def github_project_url(full_path)
"#{github_root_url}/#{full_path}"
end
def github_root_url
......@@ -49,7 +49,7 @@ module ImportHelper
@github_url = provider.fetch('url', 'https://github.com') if provider
end
def gitea_project_url(path_with_namespace)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{path_with_namespace}"
def gitea_project_url(full_path)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}"
end
end
......@@ -99,7 +99,7 @@ module IssuablesHelper
project = Project.find_by(id: project_id)
if project
project.name_with_namespace
project.full_name
else
default_label
end
......
......@@ -99,13 +99,13 @@ module ProjectsHelper
end
def remove_project_message(project)
_("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
_("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_full_name: project.full_name }
end
def transfer_project_message(project)
_("You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
_("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") %
{ project_full_name: project.full_name }
end
def remove_fork_project_message(project)
......
......@@ -120,7 +120,7 @@ module SearchHelper
category: "Projects",
id: p.id,
value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.name_with_namespace)}",
label: "#{search_result_sanitize(p.full_name)}",
url: project_path(p)
}
end
......
......@@ -114,7 +114,7 @@ module TodosHelper
projects = current_user.authorized_projects.sorted_by_activity.non_archived.with_route
projects = projects.map do |project|
{ id: project.id, text: project.name_with_namespace }
{ id: project.id, text: project.full_name }
end
projects.unshift({ id: '', text: 'Any Project' }).to_json
......
......@@ -19,6 +19,7 @@ module Issuable
include AfterCommitQueue
include Sortable
include CreatedAtFilterable
include UpdatedAtFilterable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
......
module UpdatedAtFilterable
extend ActiveSupport::Concern
included do
scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) }
scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) }
def self.scoped_table
arel_table.alias(table_name)
end
end
end
......@@ -164,7 +164,7 @@ class Event < ActiveRecord::Base
def project_name
if project
project.name_with_namespace
project.full_name
else
"(deleted project)"
end
......
......@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
def commits_count
super || merge_request_diff_commits.size
end
private
def create_merge_request_diff_files(diffs)
......
......@@ -68,7 +68,7 @@ http://app.asana.com/-/account_api'
end
user = data[:user_name]
project_name = project.name_with_namespace
project_name = project.full_name
data[:commits].each do |commit|
push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
......
......@@ -86,7 +86,7 @@ class CampfireService < Service
after = push[:after]
message = ""
message << "[#{project.name_with_namespace}] "
message << "[#{project.full_name}] "
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
......
......@@ -129,7 +129,7 @@ class ChatNotificationService < Service
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
project.full_name.gsub(/\s/, '')
end
def project_url
......
......@@ -120,7 +120,7 @@ class HipchatService < Service
else
message << "pushed to #{ref_type} <a href=\""\
"#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
push[:commits].take(MAX_COMMITS).each do |commit|
......@@ -275,7 +275,7 @@ class HipchatService < Service
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
project.full_name.gsub(/\s/, '')
end
def project_url
......
class JiraService < IssueTrackerService
include Gitlab::Routing
include ApplicationHelper
include ActionView::Helpers::AssetUrlHelper
validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
......@@ -268,7 +270,9 @@ class JiraService < IssueTrackerService
url: url,
title: title,
status: status,
icon: { title: 'GitLab', url16x16: 'https://gitlab.com/favicon.ico' }
icon: {
title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url)
}
}
}
end
......
......@@ -37,7 +37,7 @@ class MattermostSlashCommandsService < SlashCommandsService
private
def command(params)
pretty_project_name = project.name_with_namespace
pretty_project_name = project.full_name
params.merge(
auto_complete: true,
......
......@@ -88,10 +88,10 @@ class PushoverService < Service
user: user_key,
device: device,
priority: priority,
title: "#{project.name_with_namespace}",
title: "#{project.full_name}",
message: message,
url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}"
url_title: "See project #{project.full_name}"
}
# Sound parameter MUST NOT be sent to API if not selected
......
......@@ -260,7 +260,7 @@ class Repository
# branches or tags, but we want to keep some of these commits around, for
# example if they have comments or CI builds.
def keep_around(sha)
return unless sha && commit_by(oid: sha)
return unless sha.present? && commit_by(oid: sha)
return if kept_around?(sha)
......
......@@ -22,8 +22,8 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
created_at: model.created_at.xmlschema,
updated_at: model.updated_at.xmlschema
created_at: model.created_at&.xmlschema,
updated_at: model.updated_at&.xmlschema
}
case model
......
......@@ -190,7 +190,7 @@
%h4 Latest projects
- @projects.each do |project|
%p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
%span.light.pull-right
#{time_ago_with_tooltip(project.created_at)}
.col-md-4
......
......@@ -82,7 +82,7 @@
- @projects.each do |project|
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
......@@ -100,7 +100,7 @@
- @group.shared_projects.sort_by(&:name).each do |project|
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
......
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.name_with_namespace
- page_title @project.name_with_namespace, "Projects"
- breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects"
%h3.page-title
Project: #{@project.name_with_namespace}
Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr pull-right" do
%i.fa.fa-pencil-square-o
Edit
......
......@@ -39,7 +39,7 @@
%tr.alert-info
%td
%strong
= project.name_with_namespace
= project.full_name
%td
.pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
......@@ -61,7 +61,7 @@
- @projects.each do |project|
%tr
%td
= project.name_with_namespace
= project.full_name
%td
.pull-right
= form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
......@@ -95,7 +95,7 @@
%td.status
- if project
= project.name_with_namespace
= project.full_name
%td.build-link
- if project
......
......@@ -29,12 +29,12 @@
.panel.panel-default
.panel-heading Joined projects (#{@joined_projects.count})
%ul.well-list
- @joined_projects.sort_by(&:name_with_namespace).each do |project|
- @joined_projects.sort_by(&:full_name).each do |project|
- member = project.team.find_member(@user.id)
%li.project_member
.list-item-name
= link_to admin_project_path(project), class: dom_class(project) do
= project.name_with_namespace
= project.full_name
- if member
.pull-right
......
......@@ -14,7 +14,7 @@
.list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
%strong= link_to project.name_with_namespace, project
%strong= link_to project.full_name, project
.pull-right
- if project.archived
%span.label.label-warning archived
......
......@@ -12,7 +12,7 @@
- project = @member.source
project
%strong
= link_to project.name_with_namespace, project_url(project)
= link_to project.full_name, project_url(project)
- when Group
- group = @member.source
group
......
- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
- project_meta = { id: @project.id, name: @project.name, namespace: @project.full_name, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
.projects-dropdown-container
.project-dropdown-sidebar.qa-projects-dropdown-sidebar
%ul
......
- page_title @project.name_with_namespace
- page_title @project.full_name
- page_description @project.description unless page_description
- header_title project_title(@project) unless header_title
- nav "project"
......
......@@ -3,6 +3,6 @@
%p
The project export can be downloaded from:
= link_to download_export_project_url(@project), rel: 'nofollow', download: '' do
= @project.name_with_namespace + " export"
= @project.full_name + " export"
%p
The download link will expire in 24 hours.
......@@ -3,7 +3,7 @@
%p
The project is now located under
= link_to project_url(@project) do
= @project.name_with_namespace
= @project.full_name
%p
To update the remote url in your local repository run (for ssh):
%p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" }
......
......@@ -4,7 +4,7 @@
%td
%strong
- if can?(current_user, :read_project, project)
= link_to project.name_with_namespace, project_path(project)
= link_to project.full_name, project_path(project)
- else
.light N/A
%td
......
......@@ -12,7 +12,9 @@
Add an SSH key
%p.profile-settings-content
Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh/README")
= link_to "generate one", help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
or use an
= link_to "existing key.", help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
= render 'form'
%hr
%h5
......
......@@ -63,7 +63,7 @@
- if admin
%td
- if job.project
= link_to job.project.name_with_namespace, admin_project_path(job.project)
= link_to job.project.full_name, admin_project_path(job.project)
%td
- if job.try(:runner)
= runner_link(job.runner)
......
......@@ -53,7 +53,7 @@
- if admin
%td
- if generic_commit_status.project
= link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project)
= link_to generic_commit_status.project.full_name, admin_project_path(generic_commit_status.project)
%td
- if generic_commit_status.try(:runner)
= runner_link(generic_commit_status.runner)
......
......@@ -18,7 +18,7 @@
- unless @issue.project.id == merge_request.target_project.id
in
- project = merge_request.target_project
= link_to project.name_with_namespace, project_path(project)
= link_to project.full_name, project_path(project)
- if merge_request.merged?
%span.merge-request-status.prepend-left-10.merged
......
- run_actions_text = "Perform common operations on GitLab project: #{@project.name_with_namespace}"
- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}"
%p To setup this service:
%ul.list-unstyled.indent-list
......@@ -20,7 +20,7 @@
.form-group
= label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label'
.col-sm-10.col-xs-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly'
= text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control input-sm', readonly: 'readonly'
.input-group-btn
= clipboard_button(target: '#display_name')
......
- pretty_name = defined?(@project) ? @project.name_with_namespace : 'namespace / path'
- pretty_name = defined?(@project) ? @project.full_name : 'namespace / path'
- run_actions_text = "Perform common operations on GitLab project: #{pretty_name}"
.well
......
......@@ -22,7 +22,7 @@
%span.dropdown-toggle-text
Project:
- if @project.present?
= @project.name_with_namespace
= @project.full_name
- else
Any
= icon("chevron-down")
......
......@@ -7,7 +7,7 @@
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]}
- elsif @group
in group #{link_to @group.name, @group}
= render 'shared/promotions/promote_advanced_search'
......
......@@ -10,4 +10,4 @@
.description.term
= search_md_sanitize(issue, :description)
%span.light
#{issue.project.name_with_namespace}
#{issue.project.full_name}
......@@ -11,4 +11,4 @@
.description.term
= search_md_sanitize(merge_request, :description)
%span.light
#{merge_request.project.name_with_namespace}
#{merge_request.project.full_name}
......@@ -7,7 +7,7 @@
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
commented on
= link_to project.name_with_namespace, project
= link_to project.full_name, project
&middot;
- if note.for_commit?
......
......@@ -11,7 +11,7 @@
%small.pull-right.cgray
- if snippet_title.project_id?
= link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project)
= link_to snippet_title.project.full_name, project_path(snippet_title.project)
.snippet-info
= snippet_title.to_reference
......
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.name_with_namespace
- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
%h3.page-title
Unsubscribe from #{noteable_type}
......
......@@ -12,7 +12,7 @@
- if show_project_name
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
%strong #{project.full_name} &middot;
- if issuable.is_a?(Issue)
= confidential_icon(issuable)
= link_to issuable.title, issuable_url_args, title: issuable.title
......
......@@ -27,7 +27,7 @@
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= dashboard ? milestone.project.name_with_namespace : milestone.project.name
= dashboard ? milestone.project.full_name : milestone.project.name
- if @group
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestones, @group)
......
......@@ -56,7 +56,7 @@
- milestone.milestones.each do |ms|
%tr
%td
- project_name = group ? ms.project.name : ms.project.name_with_namespace
- project_name = group ? ms.project.name : ms.project.full_name
= link_to project_name, project_milestone_path(ms.project, ms)
%td
= ms.issues_visible_to_user(current_user).opened.count
......
......@@ -31,7 +31,7 @@
%span.hidden-xs
in
= link_to project_path(snippet.project) do
= snippet.project.name_with_namespace
= snippet.project.full_name
.pull-right.snippet-updated-at
%span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')}
......@@ -22,7 +22,7 @@ module Gitlab
importer_class.new(object, project, client).execute
counter.increment(project: project.path_with_namespace)
counter.increment(project: project.full_path)
end
def counter
......
......@@ -44,6 +44,10 @@ class GitGarbageCollectWorker
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc
# In case pack files are deleted, release libgit2 cache and open file
# descriptors ASAP instead of waiting for Ruby garbage collection
project.cleanup
ensure
cancel_lease(lease_key, lease_uuid) if lease_key.present? && lease_uuid.present?
end
......
......@@ -16,7 +16,7 @@ module Gitlab
def report_import_time(project)
duration = Time.zone.now - project.created_at
path = project.path_with_namespace
path = project.full_path
histogram.observe({ project: path }, duration)
counter.increment
......
......@@ -30,10 +30,9 @@ class ProcessCommitWorker
end
def process_commit_message(project, commit, user, author, default = false)
# this is a GitLab generated commit message, ignore it.
return if commit.merged_merge_request?(user)
closed_issues = default ? commit.closes_issues(user) : []
# Ignore closing references from GitLab-generated commit messages.
find_closing_issues = default && !commit.merged_merge_request?(user)
closed_issues = find_closing_issues ? commit.closes_issues(user) : []
close_issues(project, user, author, commit, closed_issues) if closed_issues.any?
commit.create_cross_references!(author, closed_issues)
......
---
title: Adds updated_at filter to issues and merge_requests API
merge_request: 17417
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Render htmlentities correctly for links not supported by Rinku
merge_request:
author:
type: fixed
---
title: Add search param to Branches API
merge_request: 17005
author: bunufi
type: added
---
title: Update SSH key link to include existing keys
merge_request:
author: Brendan O'Leary
type: changed
---
title: Stop loading spinner on error of milestone update on issue
merge_request: 17507
author: Takuya Noguchi
type: fixed
---
title: Upgrade Workhorse to version 3.8.0 to support structured logging
merge_request:
author:
type: other
---
title: Count comments on diffs as contributions for the contributions calendar
merge_request: 17418
author: Riccardo Padovani
type: fixed
---
title: Use host URL to build JIRA remote link icon
merge_request:
author:
type: other
---
title: Port Labels Select dropdown to Vue
merge_request: 17411
author:
type: other
---
title: Make oauth provider login generic
merge_request: 8809
author: Horatiu Eugen Vlad
\ No newline at end of file
---
title: Release libgit2 cache and open file descriptors after `git gc` run
merge_request:
author:
type: fixed
---
title: Don't error out in system hook if user has `nil` datetime columns
merge_request:
author:
type: fixed
......@@ -23,5 +23,6 @@ warmup do |app|
end
map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do
use Gitlab::Middleware::ReleaseEnv
run Gitlab::Application
end
class CleanCommitsCountMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount')
end
def down
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180301084653) do
ActiveRecord::Schema.define(version: 20180304204842) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......
......@@ -13,6 +13,7 @@ GET /projects/:id/repository/branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `search` | string | no | Return list of branches matching the search criteria. |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
......
......@@ -46,6 +46,10 @@ GET /issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
......@@ -153,6 +157,10 @@ GET /groups/:id/issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search group issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
......@@ -261,8 +269,10 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created after the given time (inclusive) |
| `created_before` | datetime | no | Return issues created before the given time (inclusive) |
| `created_after` | datetime | no | Return issues created on or after the given time |
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
......
......@@ -41,8 +41,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
| `created_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` |
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
......@@ -158,8 +160,10 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
| `created_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
| `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
......
......@@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version |
| ---------------- | --------------------- |
| 10.4 to current | 0.2.2 |
| 10.6 to current | 0.2.3 |
| 10.4 | 0.2.2 |
| 10.3 | 0.2.1 |
| 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 |
......
......@@ -16,6 +16,10 @@ module API
render_api_error!('The branch refname is invalid', 400)
end
end
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
end
end
params do
......@@ -27,15 +31,23 @@ module API
end
params do
use :pagination
use :filter_params
end
get ':id/repository/branches' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
repository = user_project.repository
branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present paginate(branches), with: Entities::Branch, project: user_project, merged_branch_names: merged_branch_names
present(
paginate(::Kaminari.paginate_array(branches)),
with: Entities::Branch,
project: user_project,
merged_branch_names: merged_branch_names
)
end
resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
......
......@@ -32,6 +32,8 @@ module API
optional :search, type: String, desc: 'Search issues for text present in the title or description'
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time'
optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time'
optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
......
......@@ -42,6 +42,8 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time'
optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
......
......@@ -25,8 +25,8 @@ module Banzai
# period or comma for punctuation without those characters being included
# in the generated link.
#
# Rubular: http://rubular.com/r/cxjPyZc7Sb
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?<!,|\.)}
# Rubular: http://rubular.com/r/JzPhi6DCZp
LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(?<!,|\.)}
# Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
......@@ -35,53 +35,19 @@ module Banzai
TEXT_QUERY = %Q(descendant-or-self::text()[
not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
and contains(., '://')
and not(starts-with(., 'http'))
and not(starts-with(., 'ftp'))
]).freeze
PUNCTUATION_PAIRS = {
"'" => "'",
'"' => '"',
')' => '(',
']' => '[',
'}' => '{'
}.freeze
def call
return doc if context[:autolink] == false
rinku_parse
text_parse
end
private
# Run the text through Rinku as a first pass
#
# This will quickly autolink http(s) and ftp links.
#
# `@doc` will be re-parsed with the HTML String from Rinku.
def rinku_parse
# Convert the options from a Hash to a String that Rinku expects
options = tag_options(link_options)
# NOTE: We don't parse email links because it will erroneously match
# external Commit and CommitRange references.
#
# The final argument tells Rinku to link short URLs that don't include a
# period (e.g., http://localhost:3000/)
rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
return if rinku == html
# Rinku returns a String, so parse it back to a Nokogiri::XML::Document
# for further processing.
@doc = parse_html(rinku)
end
# Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
def contains_unsafe?(scheme)
return false unless scheme
scheme = scheme.strip.downcase
Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
end
# Autolinks any text matching LINK_PATTERN that Rinku didn't already
# replace
def text_parse
doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html
......@@ -97,6 +63,16 @@ module Banzai
doc
end
private
# Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
def contains_unsafe?(scheme)
return false unless scheme
scheme = scheme.strip.downcase
Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
end
def autolink_match(match)
# start by stripping out dangerous links
begin
......@@ -112,12 +88,30 @@ module Banzai
match.gsub!(/((?:&[\w#]+;)+)\z/, '')
dropped = ($1 || '').html_safe
# To match the behaviour of Rinku, if the matched link ends with a
# closing part of a matched pair of punctuation, we remove that trailing
# character unless there are an equal number of closing and opening
# characters in the link.
if match.end_with?(*PUNCTUATION_PAIRS.keys)
close_character = match[-1]
close_count = match.count(close_character)
open_character = PUNCTUATION_PAIRS[close_character]
open_count = match.count(open_character)
if open_count != close_count || open_character == close_character
dropped += close_character
match = match[0..-2]
end
end
options = link_options.merge(href: match)
content_tag(:a, match, options) + dropped
content_tag(:a, match.html_safe, options) + dropped
end
def autolink_filter(text)
text.gsub(LINK_PATTERN) { |match| autolink_match(match) }
Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
autolink_match(link)
end
end
def link_options
......
......@@ -42,8 +42,8 @@ module Gitlab
end
def find_with_user_password(login, password)
# Avoid resource intensive login checks if password is not provided
return unless password.present?
# Avoid resource intensive checks if login credentials are not provided
return unless login.present? && password.present?
# Nothing to do here if internal auth is disabled and LDAP is
# not configured
......@@ -52,14 +52,26 @@ module Gitlab
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login)
# If no user is found, or it's an LDAP server, try LDAP.
# LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
Gitlab::Auth::LDAP::Authentication.login(login, password)
elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git?
user if user.active? && user.valid_password?(password)
return if user && !user.active?
authenticators = []
if user
authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, 'database')
# Add authenticators for all identities if user is not nil
user&.identities&.each do |identity|
authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, identity.provider)
end
else
# If no user is provided, try LDAP.
# LDAP users are only authenticated via LDAP
authenticators << Gitlab::Auth::LDAP::Authentication
end
authenticators.compact!
user if authenticators.find { |auth| auth.login(login, password) }
end
end
......
# These calls help to authenticate to OAuth provider by providing username and password
#
module Gitlab
module Auth
module Database
class Authentication < Gitlab::Auth::OAuth::Authentication
def login(login, password)
return false unless Gitlab::CurrentSettings.password_authentication_enabled_for_git?
user&.valid_password?(password)
end
end
end
end
end
......@@ -7,7 +7,7 @@
module Gitlab
module Auth
module LDAP
class Authentication
class Authentication < Gitlab::Auth::OAuth::Authentication
def self.login(login, password)
return unless Gitlab::Auth::LDAP::Config.enabled?
return unless login.present? && password.present?
......@@ -28,11 +28,7 @@ module Gitlab
Gitlab::Auth::LDAP::Config.providers
end
attr_accessor :provider, :ldap_user
def initialize(provider)
@provider = provider
end
attr_accessor :ldap_user
def login(login, password)
@ldap_user = adapter.bind_as(
......@@ -62,7 +58,7 @@ module Gitlab
end
def user
return nil unless ldap_user
return unless ldap_user
Gitlab::Auth::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
......
# These calls help to authenticate to OAuth provider by providing username and password
#
module Gitlab
module Auth
module OAuth
class Authentication
attr_reader :provider, :user
def initialize(provider, user = nil)
@provider = provider
@user = user
end
def login(login, password)
raise NotImplementedError
end
end
end
end
end
......@@ -8,11 +8,28 @@ module Gitlab
"google_oauth2" => "Google"
}.freeze
def self.authentication(user, provider)
return unless user
return unless enabled?(provider)
authenticator =
case provider
when /^ldap/
Gitlab::Auth::LDAP::Authentication
when 'database'
Gitlab::Auth::Database::Authentication
end
authenticator&.new(provider, user)
end
def self.providers
Devise.omniauth_providers
end
def self.enabled?(name)
return true if name == 'database'
providers.include?(name.to_sym)
end
......
......@@ -163,7 +163,7 @@ module Gitlab
def find_by_uid_and_provider
identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity && identity.user
identity&.user
end
def build_new_user
......
......@@ -23,7 +23,7 @@ module Gitlab
mr_events = event_counts(date_from, :merge_requests)
.having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest")
note_events = event_counts(date_from, :merge_requests)
.having(action: [Event::COMMENTED], target_type: "Note")
.having(action: [Event::COMMENTED], target_type: %w(Note DiffNote))
union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events])
events = Event.find_by_sql(union.to_sql).map(&:attributes)
......
......@@ -238,9 +238,9 @@ module Gitlab
self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
@loaded_all_data = false
# Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size
end
def binary?
......@@ -255,10 +255,15 @@ module Gitlab
# memory as a Ruby string.
def load_all_data!(repository)
return if @data == '' # don't mess with submodule blobs
return @data if @loaded_all_data
Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
@data = begin
# Even if we return early, recalculate wether this blob is binary in
# case a blob was initialized as text but the full data isn't
@binary = nil
return if @loaded_all_data
@data = Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
begin
if is_enabled
repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
else
......@@ -269,7 +274,6 @@ module Gitlab
@loaded_all_data = true
@loaded_size = @data.bytesize
@binary = nil
end
def name
......
......@@ -7,6 +7,28 @@ module Gitlab
end
def new_pointers(object_limit: nil, not_in: nil)
@repository.gitaly_migrate(:blob_get_new_lfs_pointers) do |is_enabled|
if is_enabled
@repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in)
else
git_new_pointers(object_limit, not_in)
end
end
end
def all_pointers
@repository.gitaly_migrate(:blob_get_all_lfs_pointers) do |is_enabled|
if is_enabled
@repository.gitaly_blob_client.get_all_lfs_pointers(@newrev)
else
git_all_pointers
end
end
end
private
def git_new_pointers(object_limit, not_in)
@new_pointers ||= begin
rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids|
object_ids = object_ids.take(object_limit) if object_limit
......@@ -16,14 +38,12 @@ module Gitlab
end
end
def all_pointers
def git_all_pointers
rev_list.all_objects(require_path: true) do |object_ids|
Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids)
end
end
private
def rev_list
Gitlab::Git::RevList.new(@repository, newrev: @newrev)
end
......
......@@ -479,9 +479,8 @@ module Gitlab
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end
# TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled && !options[:all]
if is_enabled
gitaly_commit_client.find_commits(options)
else
raw_log(options).map { |c| Commit.decorate(self, c) }
......@@ -508,9 +507,8 @@ module Gitlab
def count_commits(options)
count_commits_options = process_count_commits_options(options)
# TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
gitaly_migrate(:count_commits) do |is_enabled|
if is_enabled && !options[:all]
if is_enabled
count_commits_by_gitaly(count_commits_options)
else
count_commits_by_shelling_out(count_commits_options)
......
......@@ -45,16 +45,7 @@ module Gitlab
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
response.flat_map do |message|
message.lfs_pointers.map do |lfs_pointer|
Gitlab::Git::Blob.new(
id: lfs_pointer.oid,
size: lfs_pointer.size,
data: lfs_pointer.data,
binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
)
end
end
map_lfs_pointers(response)
end
def get_blobs(revision_paths, limit = -1)
......@@ -80,6 +71,50 @@ module Gitlab
GitalyClient::BlobsStitcher.new(response)
end
def get_new_lfs_pointers(revision, limit, not_in)
request = Gitaly::GetNewLFSPointersRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision),
limit: limit || 0
)
if not_in.nil? || not_in == :all
request.not_in_all = true
else
request.not_in_refs += not_in
end
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request)
map_lfs_pointers(response)
end
def get_all_lfs_pointers(revision)
request = Gitaly::GetNewLFSPointersRequest.new(
repository: @gitaly_repo,
revision: encode_binary(revision)
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request)
map_lfs_pointers(response)
end
private
def map_lfs_pointers(response)
response.flat_map do |message|
message.lfs_pointers.map do |lfs_pointer|
Gitlab::Git::Blob.new(
id: lfs_pointer.oid,
size: lfs_pointer.size,
data: lfs_pointer.data,
binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
)
end
end
end
end
end
end
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