Commit 58278d0c authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'ce-to-ee-2018-02-05'

parents 5c34451b 20b7818d
...@@ -51,6 +51,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -51,6 +51,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
## Contribute to GitLab ## Contribute to GitLab
For a first-time step-by-step guide to the contribution process, see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
Thank you for your interest in contributing to GitLab. This guide details how Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone. to contribute to GitLab in a way that is efficient for everyone.
......
...@@ -72,6 +72,10 @@ gem 'net-ldap' ...@@ -72,6 +72,10 @@ gem 'net-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false gem 'gollum-lib', '~> 4.2', require: false
# Before updating this gem, check if # Before updating this gem, check if
......
...@@ -235,7 +235,7 @@ export default class FileTemplateMediator { ...@@ -235,7 +235,7 @@ export default class FileTemplateMediator {
} }
setFilename(name) { setFilename(name) {
this.$filenameInput.val(name); this.$filenameInput.val(name).trigger('change');
} }
getSelected() { getSelected() {
......
...@@ -461,7 +461,7 @@ class GfmAutoComplete { ...@@ -461,7 +461,7 @@ class GfmAutoComplete {
const accentAChar = decodeURI('%C3%80'); const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF'); const accentYChar = decodeURI('%C3%BF');
const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi'); const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
return regexp.exec(targetSubtext); return regexp.exec(targetSubtext);
} }
......
...@@ -16,3 +16,31 @@ ...@@ -16,3 +16,31 @@
background-color: $user-mention-bg-hover; background-color: $user-mention-bg-hover;
} }
} }
.gfm-color_chip {
display: inline-block;
margin: 0 0 2px 4px;
vertical-align: middle;
border-radius: 3px;
$chip-size: 0.9em;
$bg-size: $chip-size / 0.9;
$bg-pos: $bg-size / 2;
width: $chip-size;
height: $chip-size;
background: $white-light;
background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
background-size: $bg-size $bg-size;
background-position: 0 0, $bg-pos $bg-pos;
> span {
display: inline-block;
width: 100%;
height: 100%;
margin-bottom: 2px;
border-radius: 3px;
border: 1px solid $black-transparent;
}
}
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
} }
} }
.wiki-form {
.edit-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.title .edit-wiki-header { .title .edit-wiki-header {
width: 780px; width: 780px;
margin-left: auto; margin-left: auto;
......
...@@ -70,7 +70,7 @@ module UploadsActions ...@@ -70,7 +70,7 @@ module UploadsActions
end end
def build_uploader_from_params def build_uploader_from_params
uploader = uploader_class.new(model, params[:secret]) uploader = uploader_class.new(model, secret: params[:secret])
uploader.retrieve_from_store!(params[:filename]) uploader.retrieve_from_store!(params[:filename])
uploader uploader
end end
......
...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController
else else
render 'edit' render 'edit'
end end
rescue WikiPage::PageChangedError rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
@conflict = true @error = e
render 'edit' render 'edit'
end end
......
class UserCalloutsController < ApplicationController
def create
if ensure_callout.persisted?
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def ensure_callout
current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name])
end
def feature_name
params.require(:feature_name)
end
end
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
def show_gke_cluster_integration_callout?(project)
can?(current_user, :create_cluster, project) &&
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
end
private
def user_dismissed?(feature_name)
current_user&.callouts&.find_by(feature_name: feature_name)
end
end
...@@ -21,4 +21,22 @@ module WikiHelper ...@@ -21,4 +21,22 @@ module WikiHelper
add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
end end
end end
def wiki_page_errors(error)
return unless error
content_tag(:div, class: 'alert alert-danger') do
case error
when WikiPage::PageChangedError
page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
concat(
(s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
)
when WikiPage::PageRenameError
s_("WikiEdit|There is already a page with the same title in that path.")
else
error.message
end
end
end
end end
...@@ -87,20 +87,10 @@ module Storage ...@@ -87,20 +87,10 @@ module Storage
remove_exports! remove_exports!
end end
def remove_exports! def remove_legacy_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was FileUtils.rm_rf(legacy_export_path)
if parent
parent.full_path + '/' + path_was
else
path_was
end
end end
end end
end end
...@@ -312,12 +312,6 @@ class Group < Namespace ...@@ -312,12 +312,6 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
def group_member(user) def group_member(user)
if group_members.loaded? if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id } group_members.find { |gm| gm.user_id == user.id }
......
...@@ -36,9 +36,8 @@ class Key < ActiveRecord::Base ...@@ -36,9 +36,8 @@ class Key < ActiveRecord::Base
after_destroy :refresh_user_cache after_destroy :refresh_user_cache
def key=(value) def key=(value)
value&.delete!("\n\r") write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)
value.strip! unless value.blank?
write_attribute(:key, value)
@public_key = nil @public_key = nil
end end
...@@ -100,7 +99,7 @@ class Key < ActiveRecord::Base ...@@ -100,7 +99,7 @@ class Key < ActiveRecord::Base
def generate_fingerprint def generate_fingerprint
self.fingerprint = nil self.fingerprint = nil
return unless self.key.present? return unless public_key.valid?
self.fingerprint = public_key.fingerprint self.fingerprint = public_key.fingerprint
end end
......
...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base ...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base
feature_available?(:multiple_issue_boards) feature_available?(:multiple_issue_boards)
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
# Exports belonging to projects with legacy storage are placed in a common
# subdirectory of the namespace, so a simple `rm -rf` is sufficient to remove
# them.
#
# Exports of projects using hashed storage are placed in a location defined
# only by the project ID, so each must be removed individually.
def remove_exports!
remove_legacy_exports!
all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
end
private private
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
......
...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base ...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
before_destroy :remove_private_deploy_keys before_destroy :remove_private_deploy_keys
after_destroy -> { run_after_commit { remove_pages } } after_destroy -> { run_after_commit { remove_pages } }
after_destroy :remove_exports
after_validation :check_pending_delete after_validation :check_pending_delete
...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base ...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base
end end
def export_path def export_path
return nil unless namespace.present? || hashed_storage?(:repository)
File.join(Gitlab::ImportExport.storage_path, disk_path) File.join(Gitlab::ImportExport.storage_path, disk_path)
end end
...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base ...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base
end end
def remove_exports def remove_exports
_, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) return nil unless export_path.present?
status.zero?
FileUtils.rm_rf(export_path)
end end
def full_path_slug def full_path_slug
......
...@@ -131,6 +131,8 @@ class ProjectWiki ...@@ -131,6 +131,8 @@ class ProjectWiki
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
return unless page
wiki.delete_page(page.path, commit_details(:deleted, message, page.title)) wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_elastic_index update_elastic_index
...@@ -145,6 +147,8 @@ class ProjectWiki ...@@ -145,6 +147,8 @@ class ProjectWiki
end end
def page_title_and_dir(title) def page_title_and_dir(title)
return unless title
title_array = title.split("/") title_array = title.split("/")
title = title_array.pop title = title_array.pop
[title, title_array.join("/")] [title, title_array.join("/")]
......
...@@ -180,15 +180,7 @@ class Repository ...@@ -180,15 +180,7 @@ class Repository
end end
def find_branch(name, fresh_repo: true) def find_branch(name, fresh_repo: true)
# Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may raw_repository.find_branch(name, fresh_repo)
# cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
# a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
# may cause the branch to "disappear" erroneously or have the wrong SHA.
#
# See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
raw_repo = fresh_repo ? initialize_raw_repository : raw_repository
raw_repo.find_branch(name)
end end
def find_tag(name) def find_tag(name)
...@@ -728,11 +720,11 @@ class Repository ...@@ -728,11 +720,11 @@ class Repository
end end
def branch_names_contains(sha) def branch_names_contains(sha)
refs_contains_sha('branch', sha) raw_repository.branch_names_contains_sha(sha)
end end
def tag_names_contains(sha) def tag_names_contains(sha)
refs_contains_sha('tag', sha) raw_repository.tag_names_contains_sha(sha)
end end
def local_branches def local_branches
......
...@@ -33,7 +33,7 @@ class Upload < ActiveRecord::Base ...@@ -33,7 +33,7 @@ class Upload < ActiveRecord::Base
end end
def build_uploader def build_uploader
uploader_class.new(model).tap do |uploader| uploader_class.new(model, mount_point, **uploader_context).tap do |uploader|
uploader.upload = self uploader.upload = self
uploader.retrieve_from_store!(identifier) uploader.retrieve_from_store!(identifier)
end end
...@@ -43,6 +43,13 @@ class Upload < ActiveRecord::Base ...@@ -43,6 +43,13 @@ class Upload < ActiveRecord::Base
File.exist?(absolute_path) File.exist?(absolute_path)
end end
def uploader_context
{
identifier: identifier,
secret: secret
}.compact
end
private private
def checksummable? def checksummable?
...@@ -67,11 +74,15 @@ class Upload < ActiveRecord::Base ...@@ -67,11 +74,15 @@ class Upload < ActiveRecord::Base
!path.start_with?('/') !path.start_with?('/')
end end
def uploader_class
Object.const_get(uploader)
end
def identifier def identifier
File.basename(path) File.basename(path)
end end
def uploader_class def mount_point
Object.const_get(uploader) super&.to_sym
end end
end end
...@@ -137,6 +137,7 @@ class User < ActiveRecord::Base ...@@ -137,6 +137,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute' has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# #
......
class UserCallout < ActiveRecord::Base
belongs_to :user
enum feature_name: {
gke_cluster_integration: 1
}
validates :user, presence: true
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: UserCallout.feature_names.keys }
end
class WikiPage class WikiPage
PageChangedError = Class.new(StandardError) PageChangedError = Class.new(StandardError)
PageRenameError = Class.new(StandardError)
include ActiveModel::Validations include ActiveModel::Validations
include ActiveModel::Conversion include ActiveModel::Conversion
...@@ -102,7 +103,7 @@ class WikiPage ...@@ -102,7 +103,7 @@ class WikiPage
# The hierarchy of the directory this page is contained in. # The hierarchy of the directory this page is contained in.
def directory def directory
wiki.page_title_and_dir(slug).last wiki.page_title_and_dir(slug)&.last.to_s
end end
# The processed/formatted content of this page. # The processed/formatted content of this page.
...@@ -177,7 +178,7 @@ class WikiPage ...@@ -177,7 +178,7 @@ class WikiPage
# Creates a new Wiki Page. # Creates a new Wiki Page.
# #
# attr - Hash of attributes to set on the new page. # attr - Hash of attributes to set on the new page.
# :title - The title for the new page. # :title - The title (optionally including dir) for the new page.
# :content - The raw markup content. # :content - The raw markup content.
# :format - Optional symbol representing the # :format - Optional symbol representing the
# content format. Can be any type # content format. Can be any type
...@@ -189,7 +190,7 @@ class WikiPage ...@@ -189,7 +190,7 @@ class WikiPage
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def create(attrs = {}) def create(attrs = {})
@attributes.merge!(attrs) update_attributes(attrs)
save(page_details: title) do save(page_details: title) do
wiki.create_page(title, content, format, message) wiki.create_page(title, content, format, message)
...@@ -204,23 +205,28 @@ class WikiPage ...@@ -204,23 +205,28 @@ class WikiPage
# See ProjectWiki::MARKUPS Hash for available formats. # See ProjectWiki::MARKUPS Hash for available formats.
# :message - Optional commit message to set on the new version. # :message - Optional commit message to set on the new version.
# :last_commit_sha - Optional last commit sha to validate the page unchanged. # :last_commit_sha - Optional last commit sha to validate the page unchanged.
# :title - The Title to replace existing title # :title - The Title (optionally including dir) to replace existing title
# #
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def update(attrs = {}) def update(attrs = {})
last_commit_sha = attrs.delete(:last_commit_sha) last_commit_sha = attrs.delete(:last_commit_sha)
if last_commit_sha && last_commit_sha != self.last_commit_sha if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.") raise PageChangedError.new("WikiEdit|You are attempting to update a page that has changed since you started editing it.")
end end
attrs.slice!(:content, :format, :message, :title) update_attributes(attrs)
@attributes.merge!(attrs)
page_details = if title_changed?
if title.present? && @page.title != title page_details = title
title
if wiki.find_page(page_details).present?
@attributes[:title] = @page.url_path
raise PageRenameError.new("WikiEdit|There is already a page with the same title in that path.")
end
else else
@page.url_path page_details = @page.url_path
end end
save(page_details: page_details) do save(page_details: page_details) do
...@@ -255,8 +261,44 @@ class WikiPage ...@@ -255,8 +261,44 @@ class WikiPage
page.version.to_s page.version.to_s
end end
def title_changed?
title.present? && self.class.unhyphenize(@page.url_path) != title
end
private private
# Process and format the title based on the user input.
def process_title(title)
return if title.blank?
title = deep_title_squish(title)
current_dirname = File.dirname(title)
if @page.present?
return title[1..-1] if current_dirname == '/'
return File.join([directory.presence, title].compact) if current_dirname == '.'
end
title
end
# This method squishes all the filename
# i.e: ' foo / bar / page_name' => 'foo/bar/page_name'
def deep_title_squish(title)
components = title.split(File::SEPARATOR).map(&:squish)
File.join(components)
end
# Updates the current @attributes hash by merging a hash of params
def update_attributes(attrs)
attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
attrs.slice!(:content, :format, :message, :title)
@attributes.merge!(attrs)
end
def set_attributes def set_attributes
attributes[:slug] = @page.url_path attributes[:slug] = @page.url_path
attributes[:title] = @page.title attributes[:title] = @page.title
......
...@@ -46,11 +46,11 @@ class FileMover ...@@ -46,11 +46,11 @@ class FileMover
end end
def uploader def uploader
@uploader ||= PersonalFileUploader.new(model, secret) @uploader ||= PersonalFileUploader.new(model, secret: secret)
end end
def temp_file_uploader def temp_file_uploader
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret) @temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
end end
def revert def revert
......
...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader ...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader
attr_accessor :model attr_accessor :model
def initialize(model, secret = nil) def initialize(model, mounted_as = nil, **uploader_context)
super(model, nil, **uploader_context)
@model = model @model = model
@secret = secret apply_context!(uploader_context)
end end
def base_dir def base_dir
...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader ...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader
self.file.filename self.file.filename
end end
# the upload does not hold the secret, but holds the path
# which contains the secret: extract it
def upload=(value) def upload=(value)
super
return unless value
return if apply_context!(value.uploader_context)
# fallback to the regex based extraction
if matches = DYNAMIC_PATH_PATTERN.match(value.path) if matches = DYNAMIC_PATH_PATTERN.match(value.path)
@secret = matches[:secret] @secret = matches[:secret]
@identifier = matches[:identifier] @identifier = matches[:identifier]
end end
super
end end
def secret def secret
...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader ...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader
private private
def apply_context!(uploader_context)
@secret, @identifier = uploader_context.values_at(:secret, :identifier)
!!(@secret && @identifier)
end
def build_upload
super.tap do |upload|
upload.secret = secret
end
end
def markdown_name def markdown_name
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]") (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end end
......
...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
delegate :base_dir, :file_storage?, to: :class delegate :base_dir, :file_storage?, to: :class
def initialize(model, mounted_as = nil, **uploader_context)
super(model, mounted_as)
end
def file_cache_storage? def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File) cache_storage.is_a?(CarrierWave::Storage::File)
end end
......
...@@ -24,7 +24,7 @@ module RecordsUploads ...@@ -24,7 +24,7 @@ module RecordsUploads
uploads.where(path: upload_path).delete_all uploads.where(path: upload_path).delete_all
upload.destroy! if upload upload.destroy! if upload
self.upload = build_upload_from_uploader(self) self.upload = build_upload
upload.save! upload.save!
end end
end end
...@@ -39,12 +39,13 @@ module RecordsUploads ...@@ -39,12 +39,13 @@ module RecordsUploads
Upload.order(id: :desc).where(uploader: self.class.to_s) Upload.order(id: :desc).where(uploader: self.class.to_s)
end end
def build_upload_from_uploader(uploader) def build_upload
Upload.new( Upload.new(
size: uploader.file.size, uploader: self.class.to_s,
path: uploader.upload_path, size: file.size,
model: uploader.model, path: upload_path,
uploader: uploader.class.to_s model: model,
mount_point: mounted_as
) )
end end
......
...@@ -9,7 +9,13 @@ ...@@ -9,7 +9,13 @@
.form-group .form-group
.col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12= f.text_field :title, class: 'form-control', value: @page.title .col-sm-12
= f.text_field :title, class: 'form-control', value: @page.title
- if @page.persisted?
%span.edit-wiki-page-slug-tip
= icon('lightbulb-o')
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
.form-group .form-group
.col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12 .col-sm-12
......
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki") - page_title _("Edit"), @page.title.capitalize, _("Wiki")
- if @conflict = wiki_page_errors(@error)
.alert.alert-danger
- page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
= (s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......
---
title: Report the correct version and revision for Geo node status requests
merge_request: 4353
author:
type: fixed
---
title: Add Colors to GitLab Flavored Markdown
merge_request: 16095
author: Tony Rom <thetonyrom@gmail.com>
type: added
---
title: Show coverage to two decimal points in coverage badge
merge_request: 10083
author: Jeff Stubler
type: changed
---
title: Sanitize extra blank spaces used when uploading a SSH key
merge_request: 40552
author:
type: fixed
---
title: Fix export removal for hashed-storage projects within a renamed or deleted
namespace
merge_request: 16658
author:
type: fixed
---
title: Added uploader metadata to the uploads.
merge_request: 16779
author:
type: added
---
title: Fix forking projects when no restricted visibility levels are defined applicationwide
merge_request: 16881
author:
type: fixed
---
title: Allow moving wiki pages from the UI
merge_request: 16313
author:
type: fixed
---
title: Trigger change event on filename input when file template is applied
merge_request: 16911
author: Sebastian Klingler
type: fixed
---
title: Add backend for persistently dismissably callouts
merge_request:
author:
type: added
...@@ -193,12 +193,6 @@ production: &base ...@@ -193,12 +193,6 @@ production: &base
# endpoint: 'http://127.0.0.1:9000' # default: nil # endpoint: 'http://127.0.0.1:9000' # default: nil
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
## Uploads (attachments, avatars, etc...)
uploads:
# The location where uploads objects are stored (default: public/).
# storage_path: public/
# base_dir: uploads/-/system
## GitLab Pages ## GitLab Pages
pages: pages:
enabled: false enabled: false
......
...@@ -35,6 +35,88 @@ module Gollum ...@@ -35,6 +35,88 @@ module Gollum
[] []
end end
end end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def update_page(page, name, format, data, commit = {})
name = name ? ::File.basename(name) : page.name
format ||= page.format
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
filename = (rename = page.name != name) ? Gollum::Page.cname(name) : page.filename_stripped
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
if !rename && page.format == format
committer.add(page.path, normalize(data))
else
committer.delete(page.path)
committer.add_to_index(dir, filename, format, data)
end
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
index.update_working_dir(dir, filename, format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def rename_page(page, rename, commit = {})
return false if page.nil?
return false if rename.nil? || rename.empty?
(target_dir, target_name) = ::File.split(rename)
(source_dir, source_name) = ::File.split(page.path)
source_name = page.filename_stripped
# File.split gives us relative paths with ".", commiter.add_to_index doesn't like that.
target_dir = '' if target_dir == '.'
source_dir = '' if source_dir == '.'
target_dir = target_dir.gsub(/^\//, '') # rubocop:disable Style/RegexpLiteral
# if the rename is a NOOP, abort
if source_dir == target_dir && source_name == target_name
return false
end
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
# This piece only works for multi_commit
# If we are in a commit batch and one of the previous operations
# has updated the page, any information we ask to the page can be outdated.
# Therefore, we should ask first to the current committer tree to see if
# there is any updated change.
raw_data = raw_data_in_committer(committer, source_dir, page.filename) ||
raw_data_in_committer(committer, source_dir, "#{target_name}.#{Page.format_to_ext(page.format)}") ||
page.raw_data
committer.delete(page.path)
committer.add_to_index(target_dir, target_name, page.format, raw_data)
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(source_dir, source_name, page.format)
index.update_working_dir(target_dir, target_name, page.format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def raw_data_in_committer(committer, dir, filename)
data = nil
[*dir.split(::File::SEPARATOR), filename].each do |key|
data = data ? data[key] : committer.tree[key]
break unless data
end
data
end
end end
module Git module Git
......
...@@ -75,6 +75,9 @@ Rails.application.routes.draw do ...@@ -75,6 +75,9 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update] resources :issues, module: :boards, only: [:index, :update]
end end
# UserCallouts
resources :user_callouts, only: [:create]
end end
# Koding route # Koding route
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateUserCallouts < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :user_callouts do |t|
t.integer :feature_name, null: false
t.references :user, index: true, foreign_key: { on_delete: :cascade }, null: false
end
add_index :user_callouts, [:user_id, :feature_name], unique: true
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddUploadsBuilderContext < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :uploads, :mount_point, :string
add_column :uploads, :secret, :string
end
end
# Copy of 20180202111106 - this one should run before 20171207150343 to fix issues related to
# the removal of groups with labels.
class RemoveProjectLabelsGroupIdCopy < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
# rubocop:disable Migration/UpdateColumnInBatches
update_column_in_batches(:labels, :group_id, nil) do |table, query|
query.where(table[:type].eq('ProjectLabel').and(table[:group_id].not_eq(nil)))
end
# rubocop:enable Migration/UpdateColumnInBatches
end
def down
end
end
...@@ -999,6 +999,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -999,6 +999,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.integer "job_artifacts_count" t.integer "job_artifacts_count"
t.integer "job_artifacts_synced_count" t.integer "job_artifacts_synced_count"
t.integer "job_artifacts_failed_count" t.integer "job_artifacts_failed_count"
t.string "version"
t.string "revision"
end end
add_index "geo_node_statuses", ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree add_index "geo_node_statuses", ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree
...@@ -2253,6 +2255,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2253,6 +2255,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.string "uploader", null: false t.string "uploader", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.integer "store" t.integer "store"
t.string "mount_point"
t.string "secret"
end end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
...@@ -2271,6 +2275,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2271,6 +2275,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
create_table "user_callouts", force: :cascade do |t|
t.integer "feature_name", null: false
t.integer "user_id", null: false
end
add_index "user_callouts", ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
add_index "user_callouts", ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
create_table "user_custom_attributes", force: :cascade do |t| create_table "user_custom_attributes", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
...@@ -2604,6 +2616,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2604,6 +2616,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users" add_foreign_key "u2f_registrations", "users"
add_foreign_key "user_callouts", "users", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
......
...@@ -13,7 +13,7 @@ override certain values. ...@@ -13,7 +13,7 @@ override certain values.
Variable | Type | Description Variable | Type | Description
-------- | ---- | ----------- -------- | ---- | -----------
`GITLAB_CDN_HOST` | string | Sets the hostname for a CDN to serve static assets (e.g. `mycdnsubdomain.fictional-cdn.com`) `GITLAB_CDN_HOST` | string | Sets the base URL for a CDN to serve static assets (e.g. `//mycdnsubdomain.fictional-cdn.com`)
`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
......
...@@ -109,7 +109,7 @@ keys must be manually replicated to the secondary node. ...@@ -109,7 +109,7 @@ keys must be manually replicated to the secondary node.
1. Make a backup of any existing SSH host keys: 1. Make a backup of any existing SSH host keys:
```bash ```bash
find /etc/ssh -iname ssh_host_* -exec mv {} {}.backup.`date +%F` \; find /etc/ssh -iname ssh_host_* -exec cp {} {}.backup.`date +%F` \;
``` ```
1. SSH into the **primary** node, and execute the command below: 1. SSH into the **primary** node, and execute the command below:
...@@ -118,7 +118,7 @@ keys must be manually replicated to the secondary node. ...@@ -118,7 +118,7 @@ keys must be manually replicated to the secondary node.
sudo find /etc/ssh -iname ssh_host_* -not -iname '*.pub' sudo find /etc/ssh -iname ssh_host_* -not -iname '*.pub'
``` ```
1. For each file in that list copy the file from the primary node to 1. For each file in that list replace the file from the primary node to
the **same** location on your **secondary** node. the **same** location on your **secondary** node.
1. On your **secondary** node, ensure the file permissions are correct: 1. On your **secondary** node, ensure the file permissions are correct:
......
...@@ -254,7 +254,7 @@ GFM will recognize the following: ...@@ -254,7 +254,7 @@ GFM will recognize the following:
| `@user_name` | specific user | | `@user_name` | specific user |
| `@group_name` | specific group | | `@group_name` | specific group |
| `@all` | entire team | | `@all` | entire team |
| `#123` | issue | | `#12345` | issue |
| `!123` | merge request | | `!123` | merge request |
| `$123` | snippet | | `$123` | snippet |
| `&123` | epic | | `&123` | epic |
...@@ -382,6 +382,45 @@ _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._ ...@@ -382,6 +382,45 @@ _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._
>**Note:** >**Note:**
This also works for the asciidoctor `:stem: latexmath`. For details see the [asciidoctor user manual][asciidoctor-manual]. This also works for the asciidoctor `:stem: latexmath`. For details see the [asciidoctor user manual][asciidoctor-manual].
### Colors
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colors
It is possible to have color written in HEX, RGB or HSL format rendered with a color indicator.
Color written inside backticks will be followed by a color "chip".
Examples:
`#F00`
`#F00A`
`#FF0000`
`#FF0000AA`
`RGB(0,255,0)`
`RGB(0%,100%,0%)`
`RGBA(0,255,0,0.7)`
`HSL(540,70%,50%)`
`HSLA(540,70%,50%,0.7)`
Becomes:
`#F00`
`#F00A`
`#FF0000`
`#FF0000AA`
`RGB(0,255,0)`
`RGB(0%,100%,0%)`
`RGBA(0,255,0,0.7)`
`HSL(540,70%,50%)`
`HSLA(540,70%,50%,0.7)`
#### Supported formats:
* HEX: `` `#RGB[A]` `` or `` `#RRGGBB[AA]` ``
* RGB: `` `RGB[A](R, G, B[, A])` ``
* HSL: `` `HSL[A](H, S, L[, A])` ``
### Mermaid ### Mermaid
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in
......
...@@ -46,7 +46,7 @@ namespace that started the import process. ...@@ -46,7 +46,7 @@ namespace that started the import process.
The importer will also import branches on forks of projects related to open pull The importer will also import branches on forks of projects related to open pull
requests. These branches will be imported with a naming scheme similar to requests. These branches will be imported with a naming scheme similar to
GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepency GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepancy
in branches compared to the GitHub Repository. in branches compared to the GitHub Repository.
For a more technical description and an overview of the architecture you can For a more technical description and an overview of the architecture you can
......
...@@ -64,6 +64,18 @@ effect. ...@@ -64,6 +64,18 @@ effect.
You can find the **Delete** button only when editing a page. Click on it and You can find the **Delete** button only when editing a page. Click on it and
confirm you want the page to be deleted. confirm you want the page to be deleted.
## Moving a wiki page
You can move a wiki page from one directory to another by specifying the full
path in the wiki page title in the [edit](#editing-a-wiki-page) form.
![Moving a page](img/wiki_move_page_1.png)
![After moving a page](img/wiki_move_page_2.png)
In order to move a wiki page to the root directory, the wiki page title must
be preceded by the slash (`/`) character.
## Viewing a list of all created wiki pages ## Viewing a list of all created wiki pages
Every wiki has a sidebar from which a short list of the created pages can be Every wiki has a sidebar from which a short list of the created pages can be
......
module EE module EE
module GeoHelper module GeoHelper
def node_vue_list_properties
version, revision =
if ::Gitlab::Geo.primary?
[::Gitlab::VERSION, ::Gitlab::REVISION]
else
status = ::Gitlab::Geo.primary_node&.status
[status&.version, status&.revision]
end
{
primary_version: version.to_s,
primary_revision: revision.to_s,
node_details_path: admin_geo_nodes_path.to_s,
node_actions_allowed: ::Gitlab::Database.read_write?.to_s,
node_edit_allowed: ::Gitlab::Geo.license_allows?.to_s
}
end
def node_namespaces_options(namespaces) def node_namespaces_options(namespaces)
namespaces.map { |g| { id: g.id, text: g.full_name } } namespaces.map { |g| { id: g.id, text: g.full_name } }
end end
......
...@@ -4,7 +4,7 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -4,7 +4,7 @@ class GeoNodeStatus < ActiveRecord::Base
delegate :selective_sync_type, to: :geo_node delegate :selective_sync_type, to: :geo_node
# Whether we were successful in reaching this node # Whether we were successful in reaching this node
attr_accessor :success, :version, :revision attr_accessor :success
attr_writer :health_status attr_writer :health_status
attr_accessor :storage_shards attr_accessor :storage_shards
...@@ -58,10 +58,18 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -58,10 +58,18 @@ class GeoNodeStatus < ActiveRecord::Base
GeoNodeStatus.new(HashWithIndifferentAccess.new(json_data)) GeoNodeStatus.new(HashWithIndifferentAccess.new(json_data))
end end
EXCLUDED_PARAMS = %w[id created_at].freeze
EXTRA_PARAMS = %w[
success
health
health_status
last_event_timestamp
cursor_last_event_timestamp
storage_shards
].freeze
def self.allowed_params def self.allowed_params
excluded_params = %w(id created_at updated_at).freeze self.column_names - EXCLUDED_PARAMS + EXTRA_PARAMS
extra_params = %w(success health health_status last_event_timestamp cursor_last_event_timestamp version revision storage_shards updated_at).freeze
self.column_names - excluded_params + extra_params
end end
def load_data_from_current_node def load_data_from_current_node
...@@ -83,6 +91,9 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -83,6 +91,9 @@ class GeoNodeStatus < ActiveRecord::Base
self.last_successful_status_check_at = Time.now self.last_successful_status_check_at = Time.now
self.storage_shards = StorageShard.all self.storage_shards = StorageShard.all
self.version = Gitlab::VERSION
self.revision = Gitlab::REVISION
load_primary_data load_primary_data
load_secondary_data load_secondary_data
......
...@@ -37,8 +37,10 @@ module ObjectStorage ...@@ -37,8 +37,10 @@ module ObjectStorage
super super
end end
def build_upload_from_uploader(uploader) def build_upload
super.tap { |upload| upload.store = object_store } super.tap do |upload|
upload.store = object_store
end
end end
def upload=(upload) def upload=(upload)
......
...@@ -23,6 +23,6 @@ ...@@ -23,6 +23,6 @@
.alert.alert-warning WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version. .alert.alert-warning WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version.
- if @nodes.any? - if @nodes.any?
#js-geo-nodes{ data: { primary_version: "#{Gitlab::VERSION}", primary_revision: "#{Gitlab::REVISION}", node_details_path: "#{admin_geo_nodes_path}", node_actions_allowed: "#{Gitlab::Database.read_write?}", node_edit_allowed: "#{Gitlab::Geo.license_allows?}" } } #js-geo-nodes{ data: node_vue_list_properties }
- else - else
= render 'shared/empty_states/geo' = render 'shared/empty_states/geo'
class StoreVersionAndRevisionInGeoNodeStatus < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :geo_node_statuses, :version, :string
add_column :geo_node_statuses, :revision, :string
end
end
module EE
module API
module APIGuard
module HelperMethods
extend ::Gitlab::Utils::Override
override :find_user_from_sources
def find_user_from_sources
find_user_from_access_token ||
find_user_from_job_token ||
find_user_from_warden
end
end
end
end
end
...@@ -39,10 +39,11 @@ module API ...@@ -39,10 +39,11 @@ module API
# Helper Methods for Grape Endpoint # Helper Methods for Grape Endpoint
module HelperMethods module HelperMethods
prepend EE::API::APIGuard::HelperMethods
include Gitlab::Auth::UserAuthFinders include Gitlab::Auth::UserAuthFinders
def find_current_user! def find_current_user!
user = find_user_from_access_token || find_user_from_job_token || find_user_from_warden user = find_user_from_sources
return unless user return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
...@@ -50,6 +51,10 @@ module API ...@@ -50,6 +51,10 @@ module API
user user
end end
def find_user_from_sources
find_user_from_access_token || find_user_from_warden
end
private private
# An array of scopes that were registered (using `allow_access_with_scope`) # An array of scopes that were registered (using `allow_access_with_scope`)
......
...@@ -1267,14 +1267,6 @@ module API ...@@ -1267,14 +1267,6 @@ module API
def missing_oauth_application def missing_oauth_application
object.geo_node.missing_oauth_application? object.geo_node.missing_oauth_application?
end end
def version
Gitlab::VERSION
end
def revision
Gitlab::REVISION
end
end end
class PersonalAccessToken < Grape::Entity class PersonalAccessToken < Grape::Entity
......
module Banzai
module ColorParser
ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/ # 0.0..1.0
PERCENTS = /(?:\d{1,2}|100)%/ # 00%..100%
ALPHA_CHANNEL = /(?:,\s*(?:#{ALPHA}|#{PERCENTS}))?/
BITS = /\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5])/ # 00..255
DEGS = /-?\d+(?:deg)?/i # [-]digits[deg]
RADS = /-?(?:\d+(?:\.\d+)?|\.\d+)rad/i # [-](digits[.digits] OR .digits)rad
HEX_FORMAT = /\#(?:\h{3}|\h{4}|\h{6}|\h{8})/
RGB_FORMAT = %r{
(?:rgba?
\(
(?:
(?:(?:#{BITS},\s*){2}#{BITS})
|
(?:(?:#{PERCENTS},\s*){2}#{PERCENTS})
)
#{ALPHA_CHANNEL}
\)
)
}xi
HSL_FORMAT = %r{
(?:hsla?
\(
(?:#{DEGS}|#{RADS}),\s*#{PERCENTS},\s*#{PERCENTS}
#{ALPHA_CHANNEL}
\)
)
}xi
FORMATS = [HEX_FORMAT, RGB_FORMAT, HSL_FORMAT].freeze
COLOR_FORMAT = /\A(#{Regexp.union(FORMATS)})\z/ix
# Public: Analyzes whether the String is a color code.
#
# text - The String to be parsed.
#
# Returns the recognized color String or nil if none was found.
def self.parse(text)
text if COLOR_FORMAT =~ text
end
end
end
module Banzai
module Filter
# HTML filter that renders `color` followed by a color "chip".
#
class ColorFilter < HTML::Pipeline::Filter
COLOR_CHIP_CLASS = 'gfm-color_chip'.freeze
def call
doc.css('code').each do |node|
color = ColorParser.parse(node.content)
node << color_chip(color) if color
end
doc
end
private
def color_chip(color)
checkerboard = doc.document.create_element('span', class: COLOR_CHIP_CLASS)
chip = doc.document.create_element('span', style: inline_styles(color: color))
checkerboard << chip
end
def inline_styles(color:)
"background-color: #{color};"
end
end
end
end
...@@ -7,6 +7,7 @@ module Banzai ...@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter, Filter::SanitizationFilter,
Filter::EmojiFilter, Filter::EmojiFilter,
Filter::ColorFilter,
Filter::AutolinkFilter, Filter::AutolinkFilter,
Filter::ExternalLinkFilter Filter::ExternalLinkFilter
] ]
......
...@@ -14,6 +14,7 @@ module Banzai ...@@ -14,6 +14,7 @@ module Banzai
Filter::SyntaxHighlightFilter, Filter::SyntaxHighlightFilter,
Filter::MathFilter, Filter::MathFilter,
Filter::ColorFilter,
Filter::MermaidFilter, Filter::MermaidFilter,
Filter::VideoLinkFilter, Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter, Filter::ImageLazyLoadFilter,
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
@coverage ||= raw_coverage @coverage ||= raw_coverage
return unless @coverage return unless @coverage
@coverage.to_i @coverage.to_f.round(2)
end end
def metadata def metadata
......
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
end end
def value_text def value_text
@status ? "#{@status}%" : 'unknown' @status ? ("%.2f%%" % @status) : 'unknown'
end end
def key_width def key_width
...@@ -33,7 +33,7 @@ module Gitlab ...@@ -33,7 +33,7 @@ module Gitlab
end end
def value_width def value_width
@status ? 36 : 58 @status ? 54 : 58
end end
def value_color def value_color
......
...@@ -46,7 +46,7 @@ module Gitlab ...@@ -46,7 +46,7 @@ module Gitlab
private private
def find_file(project, secret, file) def find_file(project, secret, file)
uploader = FileUploader.new(project, secret) uploader = FileUploader.new(project, secret: secret)
uploader.retrieve_from_store!(file) uploader.retrieve_from_store!(file)
uploader.file uploader.file
end end
......
# Gitaly note: JV: no RPC's here.
module Gitlab module Gitlab
module Git module Git
class Branch < Ref class Branch < Ref
......
...@@ -1343,20 +1343,23 @@ module Gitlab ...@@ -1343,20 +1343,23 @@ module Gitlab
raise CommandError.new(e) raise CommandError.new(e)
end end
def refs_contains_sha(ref_type, sha) def branch_names_contains_sha(sha)
args = %W(#{ref_type} --contains #{sha}) gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
names = run_git(args).first if is_enabled
gitaly_ref_client.branch_names_contains_sha(sha)
if names.respond_to?(:split) else
names = names.split("\n").map(&:strip) refs_contains_sha(:branch, sha)
end
names.each do |name| end
name.slice! '* '
end end
names def tag_names_contains_sha(sha)
gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
if is_enabled
gitaly_ref_client.tag_names_contains_sha(sha)
else else
[] refs_contains_sha(:tag, sha)
end
end end
end end
...@@ -1434,6 +1437,21 @@ module Gitlab ...@@ -1434,6 +1437,21 @@ module Gitlab
end end
end end
def refs_contains_sha(ref_type, sha)
args = %W(#{ref_type} --contains #{sha})
names = run_git(args).first
return [] unless names.respond_to?(:split)
names = names.split("\n").map(&:strip)
names.each do |name|
name.slice! '* '
end
names
end
def rugged_write_config(full_path:) def rugged_write_config(full_path:)
rugged.config['gitlab.fullpath'] = full_path rugged.config['gitlab.fullpath'] = full_path
end end
......
# Gitaly note: JV: no RPC's here.
#
module Gitlab module Gitlab
module Git module Git
class Tag < Ref class Tag < Ref
......
...@@ -25,8 +25,9 @@ module Gitlab ...@@ -25,8 +25,9 @@ module Gitlab
@repository.exists? @repository.exists?
end end
# Disabled because of https://gitlab.com/gitlab-org/gitaly/merge_requests/539
def write_page(name, format, content, commit_details) def write_page(name, format, content, commit_details)
@repository.gitaly_migrate(:wiki_write_page) do |is_enabled| @repository.gitaly_migrate(:wiki_write_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_write_page(name, format, content, commit_details) gitaly_write_page(name, format, content, commit_details)
gollum_wiki.clear_cache gollum_wiki.clear_cache
...@@ -47,8 +48,9 @@ module Gitlab ...@@ -47,8 +48,9 @@ module Gitlab
end end
end end
# Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42094
def update_page(page_path, title, format, content, commit_details) def update_page(page_path, title, format, content, commit_details)
@repository.gitaly_migrate(:wiki_update_page) do |is_enabled| @repository.gitaly_migrate(:wiki_update_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_update_page(page_path, title, format, content, commit_details) gitaly_update_page(page_path, title, format, content, commit_details)
gollum_wiki.clear_cache gollum_wiki.clear_cache
...@@ -68,8 +70,9 @@ module Gitlab ...@@ -68,8 +70,9 @@ module Gitlab
end end
end end
# Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42039
def page(title:, version: nil, dir: nil) def page(title:, version: nil, dir: nil)
@repository.gitaly_migrate(:wiki_find_page) do |is_enabled| @repository.gitaly_migrate(:wiki_find_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_find_page(title: title, version: version, dir: dir) gitaly_find_page(title: title, version: version, dir: dir)
else else
...@@ -192,7 +195,10 @@ module Gitlab ...@@ -192,7 +195,10 @@ module Gitlab
assert_type!(format, Symbol) assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails) assert_type!(commit_details, CommitDetails)
gollum_wiki.write_page(name, format, content, commit_details.to_h) filename = File.basename(name)
dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
gollum_wiki.write_page(filename, format, content, commit_details.to_h, dir)
nil nil
rescue Gollum::DuplicatePageError => e rescue Gollum::DuplicatePageError => e
...@@ -210,7 +216,15 @@ module Gitlab ...@@ -210,7 +216,15 @@ module Gitlab
assert_type!(format, Symbol) assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails) assert_type!(commit_details, CommitDetails)
gollum_wiki.update_page(gollum_page_by_path(page_path), title, format, content, commit_details.to_h) page = gollum_page_by_path(page_path)
committer = Gollum::Committer.new(page.wiki, commit_details.to_h)
# Instead of performing two renames if the title has changed,
# the update_page will only update the format and content and
# the rename_page will do anything related to moving/renaming
gollum_wiki.update_page(page, page.name, format, content, committer: committer)
gollum_wiki.rename_page(page, title, committer: committer)
committer.commit
nil nil
end end
......
...@@ -145,6 +145,32 @@ module Gitlab ...@@ -145,6 +145,32 @@ module Gitlab
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present? raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end end
# Limit: 0 implies no limit, thus all tag names will be returned
def tag_names_contains_sha(sha, limit: 0)
request = Gitaly::ListTagNamesContainingCommitRequest.new(
repository: @gitaly_repo,
commit_id: sha,
limit: limit
)
stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)
consume_ref_contains_sha_response(stream, :tag_names)
end
# Limit: 0 implies no limit, thus all tag names will be returned
def branch_names_contains_sha(sha, limit: 0)
request = Gitaly::ListBranchNamesContainingCommitRequest.new(
repository: @gitaly_repo,
commit_id: sha,
limit: limit
)
stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)
consume_ref_contains_sha_response(stream, :branch_names)
end
private private
def consume_refs_response(response) def consume_refs_response(response)
...@@ -215,6 +241,13 @@ module Gitlab ...@@ -215,6 +241,13 @@ module Gitlab
Gitlab::Git::Commit.decorate(@repository, hash) Gitlab::Git::Commit.decorate(@repository, hash)
end end
def consume_ref_contains_sha_response(stream, collection_name)
stream.each_with_object([]) do |response, array|
encoded_names = response.send(collection_name).map { |b| Gitlab::Git.ref_name(b) } # rubocop:disable GitlabSecurity/PublicSend
array.concat(encoded_names)
end
end
def invalid_ref!(message) def invalid_ref!(message)
raise Gitlab::Git::Repository::InvalidRef.new(message) raise Gitlab::Git::Repository::InvalidRef.new(message)
end end
......
...@@ -21,6 +21,22 @@ module Gitlab ...@@ -21,6 +21,22 @@ module Gitlab
technology(name)&.supported_sizes technology(name)&.supported_sizes
end end
def self.sanitize(key_content)
ssh_type, *parts = key_content.strip.split
return key_content if parts.empty?
parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index|
content << part
if Gitlab::SSHPublicKey.new(content).valid?
break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present
elsif parts.size == index + 1 # return original content if we've reached the last element
break key_content
end
end
end
attr_reader :key_text, :key attr_reader :key_text, :key
# Unqualified MD5 fingerprint for compatibility # Unqualified MD5 fingerprint for compatibility
...@@ -37,23 +53,23 @@ module Gitlab ...@@ -37,23 +53,23 @@ module Gitlab
end end
def valid? def valid?
key.present? key.present? && bits && technology.supported_sizes.include?(bits)
end end
def type def type
technology.name if valid? technology.name if key.present?
end end
def bits def bits
return unless valid? return if key.blank?
case type case type
when :rsa when :rsa
key.n.num_bits key.n&.num_bits
when :dsa when :dsa
key.p.num_bits key.p&.num_bits
when :ecdsa when :ecdsa
key.group.order.num_bits key.group.order&.num_bits
when :ed25519 when :ed25519
256 256
else else
......
...@@ -59,7 +59,7 @@ module Gitlab ...@@ -59,7 +59,7 @@ module Gitlab
def allowed_levels def allowed_levels
restricted_levels = Gitlab::CurrentSettings.restricted_visibility_levels restricted_levels = Gitlab::CurrentSettings.restricted_visibility_levels
self.values - restricted_levels self.values - Array(restricted_levels)
end end
def closest_allowed_level(target_level) def closest_allowed_level(target_level)
......
This diff is collapsed.
require 'spec_helper'
describe UserCalloutsController do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe "POST #create" do
subject { post :create, feature_name: feature_name, format: :json }
context 'with valid feature name' do
let(:feature_name) { UserCallout.feature_names.keys.first }
context 'when callout entry does not exist' do
it 'should create a callout entry with dismissed state' do
expect { subject }.to change { UserCallout.count }.by(1)
end
it 'should return success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when callout entry already exists' do
let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
it 'should return success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'with invalid feature name' do
let(:feature_name) { 'bogus_feature_name' }
it 'should return bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end
...@@ -12,7 +12,7 @@ describe GeoNodeStatus, :geo do ...@@ -12,7 +12,7 @@ describe GeoNodeStatus, :geo do
let!(:project_3) { create(:project) } let!(:project_3) { create(:project) }
let!(:project_4) { create(:project) } let!(:project_4) { create(:project) }
subject { described_class.current_node_status } subject(:status) { described_class.current_node_status }
before do before do
stub_current_geo_node(secondary) stub_current_geo_node(secondary)
...@@ -425,6 +425,14 @@ describe GeoNodeStatus, :geo do ...@@ -425,6 +425,14 @@ describe GeoNodeStatus, :geo do
end end
end end
describe '#version' do
it { expect(status.version).to eq(Gitlab::VERSION) }
end
describe '#revision' do
it { expect(status.revision).to eq(Gitlab::REVISION) }
end
describe '#[]' do describe '#[]' do
it 'returns values for each attribute' do it 'returns values for each attribute' do
expect(subject[:repositories_count]).to eq(4) expect(subject[:repositories_count]).to eq(4)
......
...@@ -25,6 +25,8 @@ FactoryBot.define do ...@@ -25,6 +25,8 @@ FactoryBot.define do
cursor_last_event_id 1 cursor_last_event_id 1
cursor_last_event_timestamp { Time.now.to_i } cursor_last_event_timestamp { Time.now.to_i }
last_successful_status_check_timestamp { Time.now.beginning_of_day } last_successful_status_check_timestamp { Time.now.beginning_of_day }
version { Gitlab::VERSION }
revision { Gitlab::REVISION }
end end
trait :unhealthy do trait :unhealthy do
......
...@@ -5,6 +5,10 @@ FactoryBot.define do ...@@ -5,6 +5,10 @@ FactoryBot.define do
title title
key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' } key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' }
factory :key_without_comment do
key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate }
end
factory :deploy_key, class: 'DeployKey' factory :deploy_key, class: 'DeployKey'
factory :personal_key do factory :personal_key do
......
...@@ -125,6 +125,12 @@ FactoryBot.define do ...@@ -125,6 +125,12 @@ FactoryBot.define do
avatar { fixture_file_upload('spec/fixtures/dk.png') } avatar { fixture_file_upload('spec/fixtures/dk.png') }
end end
trait :with_export do
after(:create) do |project, evaluator|
ProjectExportWorker.new.perform(project.creator.id, project.id)
end
end
trait :broken_storage do trait :broken_storage do
after(:create) do |project| after(:create) do |project|
project.update_column(:repository_storage, 'broken') project.update_column(:repository_storage, 'broken')
......
...@@ -3,26 +3,29 @@ FactoryBot.define do ...@@ -3,26 +3,29 @@ FactoryBot.define do
model { build(:project) } model { build(:project) }
size 100.kilobytes size 100.kilobytes
uploader "AvatarUploader" uploader "AvatarUploader"
mount_point :avatar
secret nil
store ObjectStorage::Store::LOCAL store ObjectStorage::Store::LOCAL
# we should build a mount agnostic upload by default # we should build a mount agnostic upload by default
transient do transient do
mounted_as :avatar filename 'myfile.jpg'
secret SecureRandom.hex
end end
# this needs to comply with RecordsUpload::Concern#upload_path # this needs to comply with RecordsUpload::Concern#upload_path
path { File.join("uploads/-/system", model.class.to_s.underscore, mounted_as.to_s, 'avatar.jpg') } path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
trait :personal_snippet_upload do trait :personal_snippet_upload do
model { build(:personal_snippet) } model { build(:personal_snippet) }
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "PersonalFileUploader" uploader "PersonalFileUploader"
secret SecureRandom.hex
end end
trait :issuable_upload do trait :issuable_upload do
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "FileUploader" uploader "FileUploader"
secret SecureRandom.hex
end end
trait :object_storage do trait :object_storage do
...@@ -31,13 +34,14 @@ FactoryBot.define do ...@@ -31,13 +34,14 @@ FactoryBot.define do
trait :namespace_upload do trait :namespace_upload do
model { build(:group) } model { build(:group) }
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "NamespaceFileUploader" uploader "NamespaceFileUploader"
secret SecureRandom.hex
end end
trait :attachment_upload do trait :attachment_upload do
transient do transient do
mounted_as :attachment mount_point :attachment
end end
model { build(:note) } model { build(:note) }
......
FactoryBot.define do
factory :user_callout do
feature_name :gke_cluster_integration
user
end
end
...@@ -261,6 +261,10 @@ describe 'GitLab Markdown' do ...@@ -261,6 +261,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links expect(doc).to parse_video_links
end end
it 'includes ColorFilter' do
expect(doc).to parse_colors
end
end end
context 'wiki pipeline' do context 'wiki pipeline' do
...@@ -323,6 +327,10 @@ describe 'GitLab Markdown' do ...@@ -323,6 +327,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links expect(doc).to parse_video_links
end end
it 'includes ColorFilter' do
expect(doc).to parse_colors
end
end end
# Fake a `current_user` helper # Fake a `current_user` helper
......
...@@ -18,7 +18,7 @@ feature 'test coverage badge' do ...@@ -18,7 +18,7 @@ feature 'test coverage badge' do
show_test_coverage_badge show_test_coverage_badge
expect_coverage_badge('95%') expect_coverage_badge('95.00%')
end end
scenario 'user requests coverage badge for specific job' do scenario 'user requests coverage badge for specific job' do
...@@ -30,7 +30,7 @@ feature 'test coverage badge' do ...@@ -30,7 +30,7 @@ feature 'test coverage badge' do
show_test_coverage_badge(job: 'coverage') show_test_coverage_badge(job: 'coverage')
expect_coverage_badge('85%') expect_coverage_badge('85.00%')
end end
scenario 'user requests coverage badge for pipeline without coverage' do scenario 'user requests coverage badge for pipeline without coverage' do
......
require 'spec_helper' require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', :js do feature 'Import/Export - Namespace export file cleanup', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
let(:project) { create(:project) } before do
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end end
after do after do
FileUtils.rm_rf(export_path, secure: true) FileUtils.rm_rf(export_path, secure: true)
end end
context 'admin user' do shared_examples_for 'handling project exports on namespace change' do
let!(:old_export_path) { project.export_path }
before do before do
sign_in(create(:admin)) sign_in(create(:admin))
end
context 'moving the namespace' do
scenario 'removes the export file' do
setup_export_project setup_export_project
end
old_export_path = project.export_path.dup context 'moving the namespace' do
it 'removes the export file' do
expect(File).to exist(old_export_path) expect(File).to exist(old_export_path)
project.namespace.update(path: 'new_path') project.namespace.update!(path: build(:namespace).path)
expect(File).not_to exist(old_export_path) expect(File).not_to exist(old_export_path)
end end
end end
context 'deleting the namespace' do context 'deleting the namespace' do
scenario 'removes the export file' do it 'removes the export file' do
setup_export_project
old_export_path = project.export_path.dup
expect(File).to exist(old_export_path) expect(File).to exist(old_export_path)
project.namespace.destroy project.namespace.destroy
...@@ -46,6 +39,19 @@ feature 'Import/Export - Namespace export file cleanup', :js do ...@@ -46,6 +39,19 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(File).not_to exist(old_export_path) expect(File).not_to exist(old_export_path)
end end
end end
end
describe 'legacy storage' do
let(:project) { create(:project) }
it_behaves_like 'handling project exports on namespace change'
end
describe 'hashed storage' do
let(:project) { create(:project, :hashed) }
it_behaves_like 'handling project exports on namespace change'
end
def setup_export_project def setup_export_project
visit edit_project_path(project) visit edit_project_path(project)
...@@ -58,5 +64,4 @@ feature 'Import/Export - Namespace export file cleanup', :js do ...@@ -58,5 +64,4 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(page).to have_content('Download export') expect(page).to have_content('Download export')
end end
end
end end
require 'spec_helper' require 'spec_helper'
describe 'User updates wiki page' do # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
describe 'User updates wiki page', :skip_gitaly_mock do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
...@@ -143,6 +144,7 @@ describe 'User updates wiki page' do ...@@ -143,6 +144,7 @@ describe 'User updates wiki page' do
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki[message]', with: 'Update home')
fill_in(:wiki_content, with: 'My awesome wiki!') fill_in(:wiki_content, with: 'My awesome wiki!')
click_button('Save changes') click_button('Save changes')
expect(page).to have_content('Home') expect(page).to have_content('Home')
...@@ -151,4 +153,74 @@ describe 'User updates wiki page' do ...@@ -151,4 +153,74 @@ describe 'User updates wiki page' do
end end
end end
end end
context 'when the page is in a subdir' do
let!(:project) { create(:project, namespace: user.namespace) }
let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
let(:page_name) { 'page_name' }
let(:page_dir) { "foo/bar/#{page_name}" }
let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) }
before do
visit(project_wiki_edit_path(project, wiki_page))
end
it 'moves the page to the root folder' do
fill_in(:wiki_title, with: "/#{page_name}")
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, page_name))
end
it 'moves the page to other dir' do
new_page_dir = "foo1/bar1/#{page_name}"
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end
it 'remains in the same place if title has not changed' do
original_path = project_wiki_path(project, wiki_page)
fill_in(:wiki_title, with: page_name)
click_button('Save changes')
expect(current_path).to eq(original_path)
end
it 'can be moved to a different dir with a different name' do
new_page_dir = "foo1/bar1/new_page_name"
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end
it 'can be renamed and moved to the root folder' do
new_name = 'new_page_name'
fill_in(:wiki_title, with: "/#{new_name}")
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_name))
end
it 'squishes the title before creating the page' do
new_page_dir = " foo1 / bar1 / #{page_name} "
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
end
end
end end
require 'spec_helper' require 'spec_helper'
describe 'User views a wiki page' do # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
describe 'User views a wiki page', :skip_gitaly_mock do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do let(:wiki_page) do
......
...@@ -288,6 +288,18 @@ However the wrapping tags cannot be mixed as such: ...@@ -288,6 +288,18 @@ However the wrapping tags cannot be mixed as such:
![My Video](/assets/videos/gitlab-demo.mp4) ![My Video](/assets/videos/gitlab-demo.mp4)
### Colors
`#F00`
`#F00A`
`#FF0000`
`#FF0000AA`
`RGB(0,255,0)`
`RGB(0%,100%,0%)`
`RGBA(0,255,0,0.7)`
`HSL(540,70%,50%)`
`HSLA(540,70%,50%,0.7)`
### Mermaid ### Mermaid
> If this is not rendered correctly, see > If this is not rendered correctly, see
......
require "spec_helper"
describe UserCalloutsHelper do
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '.show_gke_cluster_integration_callout?' do
let(:project) { create(:project) }
subject { helper.show_gke_cluster_integration_callout?(project) }
context 'when user can create a cluster' do
before do
allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
.and_return(true)
end
context 'when user has not dismissed' do
before do
allow(helper).to receive(:user_dismissed?).and_return(false)
end
it { is_expected.to be true }
end
context 'when user dismissed' do
before do
allow(helper).to receive(:user_dismissed?).and_return(true)
end
it { is_expected.to be false }
end
end
context 'when user can not create a cluster' do
before do
allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
.and_return(false)
end
it { is_expected.to be false }
end
end
end
...@@ -130,16 +130,25 @@ describe('GfmAutoComplete', function () { ...@@ -130,16 +130,25 @@ describe('GfmAutoComplete', function () {
}); });
describe('should not match special sequences', () => { describe('should not match special sequences', () => {
const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']); const shouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
const shouldNotBePrependedBy = ['`'];
flagsUseDefaultMatcher.forEach((atSign) => { flagsUseDefaultMatcher.forEach((atSign) => {
ShouldNotBeFollowedBy.forEach((followedSymbol) => { shouldNotBeFollowedBy.forEach((followedSymbol) => {
const seq = atSign + followedSymbol; const seq = atSign + followedSymbol;
it(`should not match "${seq}"`, () => { it(`should not match "${seq}"`, () => {
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null); expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
}); });
}); });
shouldNotBePrependedBy.forEach((prependedSymbol) => {
const seq = prependedSymbol + atSign;
it(`should not match "${seq}"`, () => {
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
});
});
}); });
}); });
}); });
......
require 'spec_helper'
describe Banzai::ColorParser do
describe '.parse' do
context 'HEX format' do
[
'#abc', '#ABC',
'#d2d2d2', '#D2D2D2',
'#123a', '#123A',
'#123456aa', '#123456AA'
].each do |color|
it "parses the valid hex color #{color}" do
expect(subject.parse(color)).to eq(color)
end
end
[
'#', '#1', '#12', '#12g', '#12G',
'#12345', '#r2r2r2', '#R2R2R2', '#1234567',
'# 123', '# 1234', '# 123456', '# 12345678',
'#1 2 3', '#123 4', '#12 34 56', '#123456 78'
].each do |color|
it "does not parse the invalid hex color #{color}" do
expect(subject.parse(color)).to be_nil
end
end
end
context 'RGB format' do
[
'rgb(0,0,0)', 'rgb(255,255,255)',
'rgb(0, 0, 0)', 'RGB(0,0,0)',
'rgb(0,0,0,0)', 'rgb(0,0,0,0.0)', 'rgb(0,0,0,.0)',
'rgb(0,0,0, 0)', 'rgb(0,0,0, 0.0)', 'rgb(0,0,0, .0)',
'rgb(0,0,0,1)', 'rgb(0,0,0,1.0)',
'rgba(0,0,0)', 'rgba(0,0,0,0)', 'RGBA(0,0,0)',
'rgb(0%,0%,0%)', 'rgba(0%,0%,0%,0%)'
].each do |color|
it "parses the valid rgb color #{color}" do
expect(subject.parse(color)).to eq(color)
end
end
[
'FOOrgb(0,0,0)', 'rgb(0,0,0)BAR',
'rgb(0,0,-1)', 'rgb(0,0,-0)', 'rgb(0,0,256)',
'rgb(0,0,0,-0.1)', 'rgb(0,0,0,-0.0)', 'rgb(0,0,0,-.1)',
'rgb(0,0,0,1.1)', 'rgb(0,0,0,2)',
'rgba(0,0,0,)', 'rgba(0,0,0,0.)', 'rgba(0,0,0,1.)',
'rgb(0,0,0%)', 'rgb(101%,0%,0%)'
].each do |color|
it "does not parse the invalid rgb color #{color}" do
expect(subject.parse(color)).to be_nil
end
end
end
context 'HSL format' do
[
'hsl(0,0%,0%)', 'hsl(0,100%,100%)',
'hsl(540,0%,0%)', 'hsl(-720,0%,0%)',
'hsl(0deg,0%,0%)', 'hsl(0DEG,0%,0%)',
'hsl(0, 0%, 0%)', 'HSL(0,0%,0%)',
'hsl(0,0%,0%,0)', 'hsl(0,0%,0%,0.0)', 'hsl(0,0%,0%,.0)',
'hsl(0,0%,0%, 0)', 'hsl(0,0%,0%, 0.0)', 'hsl(0,0%,0%, .0)',
'hsl(0,0%,0%,1)', 'hsl(0,0%,0%,1.0)',
'hsla(0,0%,0%)', 'hsla(0,0%,0%,0)', 'HSLA(0,0%,0%)',
'hsl(1rad,0%,0%)', 'hsl(1.1rad,0%,0%)', 'hsl(.1rad,0%,0%)',
'hsl(-1rad,0%,0%)', 'hsl(1RAD,0%,0%)'
].each do |color|
it "parses the valid hsl color #{color}" do
expect(subject.parse(color)).to eq(color)
end
end
[
'hsl(+0,0%,0%)', 'hsl(0,0,0%)', 'hsl(0,0%,0)', 'hsl(0 deg,0%,0%)',
'hsl(0,-0%,0%)', 'hsl(0,101%,0%)', 'hsl(0,-1%,0%)',
'hsl(0,0%,0%,-0.1)', 'hsl(0,0%,0%,-.1)',
'hsl(0,0%,0%,1.1)', 'hsl(0,0%,0%,2)',
'hsl(0,0%,0%,)', 'hsl(0,0%,0%,0.)', 'hsl(0,0%,0%,1.)',
'hsl(deg,0%,0%)', 'hsl(rad,0%,0%)'
].each do |color|
it "does not parse the invalid hsl color #{color}" do
expect(subject.parse(color)).to be_nil
end
end
end
end
end
require 'spec_helper'
describe Banzai::Filter::ColorFilter, lib: true do
include FilterSpecHelper
let(:color) { '#F00' }
let(:color_chip_selector) { 'code > span.gfm-color_chip > span' }
['#123', '#1234', '#123456', '#12345678',
'rgb(0,0,0)', 'RGB(0, 0, 0)', 'rgba(0,0,0,1)', 'RGBA(0,0,0,0.7)',
'hsl(270,30%,50%)', 'HSLA(270, 30%, 50%, .7)'].each do |color|
it "inserts color chip for supported color format #{color}" do
content = code_tag(color)
doc = filter(content)
color_chip = doc.at_css(color_chip_selector)
expect(color_chip.content).to be_empty
expect(color_chip.parent[:class]).to eq 'gfm-color_chip'
expect(color_chip[:style]).to eq "background-color: #{color};"
end
end
it 'ignores valid color code without backticks(code tags)' do
doc = filter(color)
expect(doc.css('span.gfm-color_chip').size).to be_zero
end
it 'ignores valid color code with prepended space' do
content = code_tag(' ' + color)
doc = filter(content)
expect(doc.css(color_chip_selector).size).to be_zero
end
it 'ignores valid color code with appended space' do
content = code_tag(color + ' ')
doc = filter(content)
expect(doc.css(color_chip_selector).size).to be_zero
end
it 'ignores valid color code surrounded by spaces' do
content = code_tag(' ' + color + ' ')
doc = filter(content)
expect(doc.css(color_chip_selector).size).to be_zero
end
it 'ignores invalid color code' do
invalid_color = '#BAR'
content = code_tag(invalid_color)
doc = filter(content)
expect(doc.css(color_chip_selector).size).to be_zero
end
def code_tag(string)
"<code>#{string}</code>"
end
end
...@@ -2,8 +2,8 @@ require 'spec_helper' ...@@ -2,8 +2,8 @@ require 'spec_helper'
describe FileSizeValidator do describe FileSizeValidator do
let(:validator) { described_class.new(options) } let(:validator) { described_class.new(options) }
let(:attachment) { AttachmentUploader.new }
let(:note) { create(:note) } let(:note) { create(:note) }
let(:attachment) { AttachmentUploader.new(note) }
describe 'options uses an integer' do describe 'options uses an integer' do
let(:options) { { maximum: 10, attributes: { attachment: attachment } } } let(:options) { { maximum: 10, attributes: { attachment: attachment } } }
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Badge::Coverage::Template do describe Gitlab::Badge::Coverage::Template do
let(:badge) { double(entity: 'coverage', status: 90) } let(:badge) { double(entity: 'coverage', status: 90.00) }
let(:template) { described_class.new(badge) } let(:template) { described_class.new(badge) }
describe '#key_text' do describe '#key_text' do
...@@ -13,7 +13,17 @@ describe Gitlab::Badge::Coverage::Template do ...@@ -13,7 +13,17 @@ describe Gitlab::Badge::Coverage::Template do
describe '#value_text' do describe '#value_text' do
context 'when coverage is known' do context 'when coverage is known' do
it 'returns coverage percentage' do it 'returns coverage percentage' do
expect(template.value_text).to eq '90%' expect(template.value_text).to eq '90.00%'
end
end
context 'when coverage is known to many digits' do
before do
allow(badge).to receive(:status).and_return(92.349)
end
it 'returns rounded coverage percentage' do
expect(template.value_text).to eq '92.35%'
end end
end end
...@@ -37,7 +47,7 @@ describe Gitlab::Badge::Coverage::Template do ...@@ -37,7 +47,7 @@ describe Gitlab::Badge::Coverage::Template do
describe '#value_width' do describe '#value_width' do
context 'when coverage is known' do context 'when coverage is known' do
it 'is narrower when coverage is known' do it 'is narrower when coverage is known' do
expect(template.value_width).to eq 36 expect(template.value_width).to eq 54
end end
end end
...@@ -113,7 +123,7 @@ describe Gitlab::Badge::Coverage::Template do ...@@ -113,7 +123,7 @@ describe Gitlab::Badge::Coverage::Template do
describe '#width' do describe '#width' do
context 'when coverage is known' do context 'when coverage is known' do
it 'returns the key width plus value width' do it 'returns the key width plus value width' do
expect(template.width).to eq 98 expect(template.width).to eq 116
end end
end end
......
...@@ -1162,7 +1162,8 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1162,7 +1162,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do
it_behaves_like 'finding a branch' it_behaves_like 'finding a branch'
it 'should reload Rugged::Repository and return master' do context 'force_reload is true' do
it 'should reload Rugged::Repository' do
expect(Rugged::Repository).to receive(:new).twice.and_call_original expect(Rugged::Repository).to receive(:new).twice.and_call_original
repository.find_branch('master') repository.find_branch('master')
...@@ -1172,6 +1173,18 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1172,6 +1173,18 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(branch.name).to eq('master') expect(branch.name).to eq('master')
end end
end end
context 'force_reload is false' do
it 'should not reload Rugged::Repository' do
expect(Rugged::Repository).to receive(:new).once.and_call_original
branch = repository.find_branch('master', force_reload: false)
expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
expect(branch.name).to eq('master')
end
end
end
end end
describe '#ref_name_for_sha' do describe '#ref_name_for_sha' do
......
require 'spec_helper'
describe Gitlab::Git::Wiki do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:wiki) { ProjectWiki.new(project, user) }
let(:gollum_wiki) { wiki.wiki }
# Remove skip_gitaly_mock flag when gitaly_find_page when
# https://gitlab.com/gitlab-org/gitaly/merge_requests/539 gets merged
describe '#page', :skip_gitaly_mock do
it 'returns the right page' do
create_page('page1', 'content')
create_page('foo/page1', 'content')
expect(gollum_wiki.page(title: 'page1', dir: '').url_path).to eq 'page1'
expect(gollum_wiki.page(title: 'page1', dir: 'foo').url_path).to eq 'foo/page1'
destroy_page('page1')
destroy_page('page1', 'foo')
end
end
def create_page(name, content)
gollum_wiki.write_page(name, :markdown, content, commit_details)
end
def commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
end
def destroy_page(title, dir = '')
page = gollum_wiki.page(title: title, dir: dir)
wiki.delete_page(page, "test commit")
end
end
...@@ -37,6 +37,41 @@ describe Gitlab::SSHPublicKey, lib: true do ...@@ -37,6 +37,41 @@ describe Gitlab::SSHPublicKey, lib: true do
end end
end end
describe '.sanitize(key_content)' do
let(:content) { build(:key).key }
context 'when key has blank space characters' do
it 'removes the extra blank space characters' do
unsanitized = content.insert(100, "\n")
.insert(40, "\r\n")
.insert(30, ' ')
sanitized = described_class.sanitize(unsanitized)
_, body = sanitized.split
expect(sanitized).not_to eq(unsanitized)
expect(body).not_to match(/\s/)
end
end
context "when key doesn't have blank space characters" do
it "doesn't modify the content" do
sanitized = described_class.sanitize(content)
expect(sanitized).to eq(content)
end
end
context "when key is invalid" do
it 'returns the original content' do
unsanitized = "ssh-foo any content=="
sanitized = described_class.sanitize(unsanitized)
expect(sanitized).to eq(unsanitized)
end
end
end
describe '#valid?' do describe '#valid?' do
subject { public_key } subject { public_key }
......
...@@ -57,6 +57,15 @@ describe Gitlab::VisibilityLevel do ...@@ -57,6 +57,15 @@ describe Gitlab::VisibilityLevel do
expect(described_class.allowed_levels) expect(described_class.allowed_levels)
.to contain_exactly(described_class::PRIVATE, described_class::PUBLIC) .to contain_exactly(described_class::PRIVATE, described_class::PUBLIC)
end end
it 'returns all levels when no visibility level was set' do
allow(described_class)
.to receive_message_chain('current_application_settings.restricted_visibility_levels')
.and_return(nil)
expect(described_class.allowed_levels)
.to contain_exactly(described_class::PRIVATE, described_class::INTERNAL, described_class::PUBLIC)
end
end end
describe '.closest_allowed_level' do describe '.closest_allowed_level' do
......
...@@ -72,15 +72,52 @@ describe Key, :mailer do ...@@ -72,15 +72,52 @@ describe Key, :mailer do
expect(build(:key)).to be_valid expect(build(:key)).to be_valid
end end
it 'accepts a key with newline charecters after stripping them' do it 'rejects the unfingerprintable key (not a key)' do
key = build(:key) expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
key.key = key.key.insert(100, "\n") end
key.key = key.key.insert(40, "\r\n")
where(:factory, :chars, :expected_sections) do
[
[:key, ["\n", "\r\n"], 3],
[:key, [' ', ' '], 3],
[:key_without_comment, [' ', ' '], 2]
]
end
with_them do
let!(:key) { create(factory) }
let!(:original_fingerprint) { key.fingerprint }
it 'accepts a key with blank space characters after stripping them' do
modified_key = key.key.insert(100, chars.first).insert(40, chars.last)
_, content = modified_key.split
key.update!(key: modified_key)
expect(key).to be_valid expect(key).to be_valid
expect(key.key.split.size).to eq(expected_sections)
expect(content).not_to match(/\s/)
expect(original_fingerprint).to eq(key.fingerprint)
end
end
end end
it 'rejects the unfingerprintable key (not a key)' do context 'validate size' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid where(:key_content, :result) do
[
[Spec::Support::Helpers::KeyGeneratorHelper.new(512).generate, false],
[Spec::Support::Helpers::KeyGeneratorHelper.new(8192).generate, false],
[Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate, true]
]
end
with_them do
it 'validates the size of the key' do
key = build(:key, key: key_content)
expect(key.valid?).to eq(result)
end
end end
end end
......
...@@ -733,4 +733,24 @@ describe Namespace do ...@@ -733,4 +733,24 @@ describe Namespace do
end end
end end
end end
describe '#remove_exports' do
let(:legacy_project) { create(:project, :with_export, namespace: namespace) }
let(:hashed_project) { create(:project, :with_export, :hashed, namespace: namespace) }
let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') }
let(:legacy_export) { legacy_project.export_project_path }
let(:hashed_export) { hashed_project.export_project_path }
it 'removes exports for legacy and hashed projects' do
allow(Gitlab::ImportExport).to receive(:storage_path) { export_path }
expect(File.exist?(legacy_export)).to be_truthy
expect(File.exist?(hashed_export)).to be_truthy
namespace.remove_exports!
expect(File.exist?(legacy_export)).to be_falsy
expect(File.exist?(hashed_export)).to be_falsy
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.
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