Commit 97f24dbd authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee-2017-12-04' into 'master'

CE upstream - Monday

Closes gitaly#802 and omnibus-gitlab#3006

See merge request gitlab-org/gitlab-ee!3624
parents 8eba4eb6 74585d54
source 'https://rubygems.org'
gem 'rails', '4.2.8'
gem 'rails', '4.2.10'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
......
......@@ -4,38 +4,38 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
actionmailer (4.2.8)
actionpack (= 4.2.8)
actionview (= 4.2.8)
activejob (= 4.2.8)
actionmailer (4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.8)
actionview (= 4.2.8)
activesupport (= 4.2.8)
actionpack (4.2.10)
actionview (= 4.2.10)
activesupport (= 4.2.10)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.8)
activesupport (= 4.2.8)
actionview (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (4.2.8)
activesupport (= 4.2.8)
activejob (4.2.10)
activesupport (= 4.2.10)
globalid (>= 0.3.0)
activemodel (4.2.8)
activesupport (= 4.2.8)
activemodel (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
activerecord (4.2.8)
activemodel (= 4.2.8)
activesupport (= 4.2.8)
activerecord (4.2.10)
activemodel (= 4.2.10)
activesupport (= 4.2.10)
arel (~> 6.0)
activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5)
activesupport (4.2.8)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
......@@ -325,8 +325,8 @@ GEM
omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
globalid (0.3.7)
activesupport (>= 4.1.0)
globalid (0.4.1)
activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
......@@ -427,7 +427,8 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.8.6)
i18n (0.9.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
......@@ -502,8 +503,8 @@ GEM
railties (>= 4, < 5.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.6)
mime-types (>= 1.16, < 4)
mail (2.7.0)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
memoist (0.16.0)
memoizable (0.4.2)
......@@ -602,8 +603,8 @@ GEM
parallel (1.12.0)
paranoia (2.3.1)
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
ast (~> 2.2)
parser (2.4.0.2)
ast (~> 2.3)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.1)
......@@ -685,16 +686,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.8)
actionmailer (= 4.2.8)
actionpack (= 4.2.8)
actionview (= 4.2.8)
activejob (= 4.2.8)
activemodel (= 4.2.8)
activerecord (= 4.2.8)
activesupport (= 4.2.8)
rails (4.2.10)
actionmailer (= 4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
activemodel (= 4.2.10)
activerecord (= 4.2.10)
activesupport (= 4.2.10)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.8)
railties (= 4.2.10)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
......@@ -707,15 +708,15 @@ GEM
rails-i18n (4.0.9)
i18n (~> 0.7)
railties (~> 4.0)
railties (4.2.8)
actionpack (= 4.2.8)
activesupport (= 4.2.8)
railties (4.2.10)
actionpack (= 4.2.10)
activesupport (= 4.2.10)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.18.0)
rake (12.1.0)
rake (12.3.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
......@@ -902,7 +903,7 @@ GEM
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.0)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
......@@ -941,7 +942,7 @@ GEM
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.3)
tzinfo (1.2.4)
thread_safe (~> 0.1)
u2f (0.2.1)
uber (0.1.0)
......@@ -1156,7 +1157,7 @@ DEPENDENCIES
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 4.2.8)
rails (= 4.2.10)
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
......
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub';
......@@ -8,6 +9,9 @@ import itemStats from './item_stats.vue';
import itemActions from './item_actions.vue';
export default {
directives: {
tooltip,
},
components: {
identicon,
itemCaret,
......@@ -112,10 +116,16 @@ export default {
</a>
</div>
<div
class="title">
class="title namespace-title">
<a
v-tooltip
:href="group.relativePath"
class="no-expand">{{group.fullName}}</a>
:title="group.fullName"
class="no-expand"
data-placement="top"
>
{{group.name}}
</a>
<span
v-if="group.permission"
class="access-type"
......
......@@ -4,7 +4,7 @@ import Poll from '../../lib/utils/poll';
import * as types from './mutation_types';
import * as utils from './utils';
import * as constants from '../constants';
import service from '../services/issue_notes_service';
import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
......
......@@ -39,7 +39,6 @@
color: $brand-info;
}
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; }
.light { color: $common-gray; }
......
......@@ -14,6 +14,5 @@
&:hover {
background-color: $user-mention-bg-hover;
text-decoration: none;
}
}
......@@ -455,6 +455,12 @@ ul.indent-list {
}
}
.namespace-title {
.tooltip-inner {
max-width: 350px;
}
}
ul.group-list-tree {
li.group-row {
&.has-description {
......
......@@ -134,19 +134,22 @@
}
.select2-search {
padding: 15px 15px 5px;
padding: $grid-size;
.select2-drop-auto-width & {
padding: 15px 15px 5px;
padding: $grid-size;
}
input {
padding: 2px 25px 2px 5px;
padding: $grid-size;
background: $white-light image-url('select2.png');
background-clip: content-box;
background-origin: content-box;
background-repeat: no-repeat;
background-position: right 0 bottom 6px;
background-position: right 0 bottom 0 !important;
border: 1px solid $input-border;
border-radius: $border-radius-default;
line-height: 16px;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
......@@ -156,11 +159,16 @@
&.select2-active {
background-color: $white-light;
background-image: image-url('select2-spinner.gif') !important;
background-origin: content-box;
background-repeat: no-repeat;
background-position: right 5px center !important;
background-position: right 6px center !important;
background-size: 16px 16px !important;
}
}
+ .select2-results {
padding-top: 0;
}
}
.select2-results {
......
......@@ -20,6 +20,11 @@
.ref-name {
font-size: 12px;
&:hover {
text-decoration: underline;
color: $gl-text-color;
}
}
}
......
......@@ -110,6 +110,10 @@
padding: 6px 10px;
border-radius: $label-border-radius;
}
&:hover .color-label {
text-decoration: underline;
}
}
&.has-labels {
......@@ -174,6 +178,14 @@
color: $gray-darkest;
}
}
&.assignee {
.author_link:hover {
.author {
text-decoration: underline;
}
}
}
}
.block-first {
......@@ -469,7 +481,6 @@
a:not(.btn-retry) {
&:hover {
color: $md-link-color;
text-decoration: none;
.avatar {
border-color: rgba($avatar-border, .2);
......
......@@ -208,7 +208,6 @@ ul.notes {
a {
color: $gl-link-color;
text-decoration: none;
}
p {
......@@ -395,6 +394,10 @@ ul.notes {
&:focus,
&:hover {
text-decoration: none;
.note-header-author-name {
text-decoration: underline;
}
}
}
......@@ -461,6 +464,10 @@ ul.notes {
.system-note-message {
white-space: normal;
}
a:hover {
text-decoration: underline;
}
}
/**
......
......@@ -73,7 +73,7 @@
.profile-link-holder {
display: inline;
a {
a:not(.text-link) {
text-decoration: none;
}
}
......
......@@ -818,6 +818,7 @@ a.deploy-project-label {
&:hover,
&:focus {
color: $gl-text-color;
text-decoration: underline;
}
}
}
......
......@@ -124,7 +124,11 @@
&:hover,
&.active {
color: $black;
text-decoration: none;
span {
text-decoration: underline;
}
}
}
......
......@@ -26,7 +26,7 @@ class UsersFinder
end
def execute
users = User.all
users = User.all.order_id_desc
users = by_username(users)
users = by_search(users)
users = by_blocked(users)
......
......@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link
def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do
icon('code-fork') + " #{text}"
icon('code-fork', class: 'append-right-5') + "#{text}"
end
end
......@@ -77,7 +77,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link
def commit_tag_link(url, text)
link_to(url, class: 'label label-gray ref-name') do
icon('tag') + " #{text}"
icon('tag', class: 'append-right-5') + "#{text}"
end
end
......
......@@ -113,7 +113,13 @@ module MarkupHelper
text = wiki_page.content
return '' unless text.present?
context = { pipeline: :wiki, project: @project, project_wiki: @project_wiki, page_slug: wiki_page.slug }
context = {
pipeline: :wiki,
project: @project,
project_wiki: @project_wiki,
page_slug: wiki_page.slug,
issuable_state_filter_enabled: true
}
html =
case wiki_page.format
......
......@@ -239,8 +239,8 @@ class Project < ActiveRecord::Base
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
format: { without: /\.{2}/,
message: 'cannot include directory traversal.' },
format: { without: /(\.{2}|\A\/)/,
message: 'cannot include leading slash or directory traversal.' },
length: { maximum: 255 },
allow_blank: true
validates :name,
......@@ -606,7 +606,7 @@ class Project < ActiveRecord::Base
def ci_config_path=(value)
# Strip all leading slashes so that //foo -> foo
super(value&.sub(%r{\A/+}, '')&.delete("\0"))
super(value&.delete("\0"))
end
def import_url=(value)
......
......@@ -263,7 +263,7 @@ class Repository
end
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
......
......@@ -506,8 +506,12 @@ class User < ActiveRecord::Base
end
def two_factor_u2f_enabled?
if u2f_registrations.loaded?
u2f_registrations.any?
else
u2f_registrations.exists?
end
end
def namespace_uniq
# Return early if username already failed the first uniqueness validation
......
......@@ -98,6 +98,12 @@ module NotificationRecipientService
self << [target.participants(user), :participating]
end
def add_mentions(user, target:)
return unless target.respond_to?(:mentioned_users)
self << [target.mentioned_users(user), :mention]
end
# Get project/group users with CUSTOM notification level
def add_custom_notifications
user_ids = []
......@@ -227,6 +233,11 @@ module NotificationRecipientService
add_subscribed_users
if [:new_issue, :new_merge_request].include?(custom_action)
# These will all be participants as well, but adding with the :mention
# type ensures that users with the mention notification level will
# receive them, too.
add_mentions(current_user, target: target)
add_labels_subscribers
end
end
......@@ -263,7 +274,7 @@ module NotificationRecipientService
def build!
# Add all users participating in the thread (author, assignee, comment authors)
add_participants(note.author)
self << [note.mentioned_users, :mention]
add_mentions(note.author, target: note)
unless note.for_personal_snippet?
# Merge project watchers
......
......@@ -282,7 +282,7 @@
= render 'projects/settings/ee/nav'
- else
= nav_link(path: %w[members#show]) do
= nav_link(controller: :project_members) do
= link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
.nav-icon-container
= sprite_icon('users')
......
......@@ -19,5 +19,5 @@
distributed with computer software, forming part of its documentation.
%p
We recommend you to
= link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link'
= link_to "add a README", add_special_file_path(@project, file_name: 'README.md')
file to the repository and GitLab will render it here instead of this message.
......@@ -3,6 +3,6 @@
%tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name
= tree_icon('folder', '755', directory.name)
= link_to path_to_directory do
%span.str-truncated= directory.name
= link_to path_to_directory, class: 'str-truncated' do
%span= directory.name
%td
......@@ -6,12 +6,12 @@
%td.tree-item-file-name
= tree_icon('file', blob.mode, blob.name)
- if external_link
= link_to path_to_file, class: 'tree-item-file-external-link js-artifact-tree-tooltip',
= link_to path_to_file, class: 'tree-item-file-external-link js-artifact-tree-tooltip str-truncated',
target: '_blank', rel: 'noopener noreferrer', title: _('Opens in a new window') do
%span.str-truncated>= blob.name
%span>= blob.name
= icon('external-link', class: 'js-artifact-tree-external-icon')
- else
= link_to path_to_file do
%span.str-truncated= blob.name
= link_to path_to_file, class: 'str-truncated' do
%span= blob.name
%td
= number_to_human_size(blob.size, precision: 2)
......@@ -8,8 +8,7 @@
%li{ class: "js-branch-#{branch.name}" }
%div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork')
= branch.name
= icon('code-fork', class: 'append-right-5') + "#{branch.name}"
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
......
......@@ -48,6 +48,6 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent"
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
......@@ -14,12 +14,12 @@
%p
Otherwise you can start with adding a
= succeed ',' do
= link_to "README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link'
= link_to "README", add_special_file_path(@project, file_name: 'README.md')
a
= succeed ',' do
= link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
= link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE')
or a
= link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link'
= link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore')
to this project.
%p
You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected.
......
......@@ -16,7 +16,7 @@
%li prevent pushes from everybody except Masters
%li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting the branch
%p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
%p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches")} and #{link_to "project permissions", help_page_path("user/permissions")}.
- if can? current_user, :admin_project, @project
= content_for :create_protected_branch
......
......@@ -16,7 +16,7 @@
%li Prevent <strong>anyone</strong> from updating the tag
%li Prevent <strong>anyone</strong> from deleting the tag
%p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}.
%p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags")}.
- if can? current_user, :admin_project, @project
= yield :create_protected_tag
......
......@@ -2,8 +2,8 @@
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
%span.str-truncated= file_name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span= file_name
%td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner'
......@@ -2,8 +2,8 @@
%td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(@path, tree_item)
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do
%span.str-truncated= path
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), class: 'str-truncated', title: path do
%span= path
%td.hidden-xs.tree-commit
%td.tree-time-ago.text-right
= render 'projects/tree/spinner'
......@@ -6,9 +6,8 @@
- git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
= _("Clone repository")
= icon('cloud-download', class: 'append-right-5')
%span= _("Clone repository")
.blocks-container
.block.block-first
......
......@@ -38,9 +38,9 @@
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
.pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
view merge requests
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
view open issues
- if current_user
......
......@@ -31,8 +31,7 @@
.note-header
.note-header-info
%a{ href: user_path(note.author) }
%span.note-header-author-name
= sanitize(note.author.name)
%span.note-header-author-name= sanitize(note.author.name)
%span.note-headline-light
= note.author.to_reference
%span.note-headline-light
......
......@@ -51,7 +51,7 @@
.cover-desc
- unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider
= link_to @user.public_email, "mailto:#{@user.public_email}"
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
- unless @user.skype.blank?
.profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do
......@@ -66,7 +66,7 @@
= icon('twitter-square')
- unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider
= link_to @user.short_website_url, @user.full_website_url
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link'
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider
= icon('map-marker')
......
......@@ -32,17 +32,15 @@ module RepositoryCheck
end
def git_fsck(repository)
path = repository.path_to_repo
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
return false unless repository.exists?
repository.raw_repository.fsck
if status.zero?
true
else
Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}")
rescue Gitlab::Git::Repository::GitError => e
Gitlab::RepositoryCheckLogger.error(e.message)
false
end
end
def has_pushes?(project)
Project.with_push.exists?(project.id)
......
---
title: Fix sending notification emails to users with the mention level set who were
mentioned in an issue or merge request description
merge_request:
author:
type: fixed
---
title: show status of gitlab reference links in wiki
merge_request: 15694
author: haseebeqx
type: added
---
title: Show only group name by default and put full namespace in tooltip in Groups
tree
merge_request: 15650
author:
type: changed
---
title: Fix typo in docs about Elasticsearch
merge_request: 15699
author: Takuya Noguchi
type: other
---
title: Prefer ci_config_path validation for leading slashes instead of sanitizing
the input
merge_request: 15672
author: Christiaan Van den Poel
type: other
---
title: Keep track of all circuitbreaker keys in a set
merge_request: 15613
author:
type: performance
---
title: Added default order to UsersFinder
merge_request: 15679
author:
type: fixed
---
title: "Gracefully handle case when repository's root ref does not exist"
merge_request:
author:
type: fixed
# WARNING changes in this file must be manually propagated to gitaly-ruby.
#
# https://gitlab.com/gitlab-org/gitaly/blob/master/ruby/lib/gitlab/gollum.rb
module Gollum
GIT_ADAPTER = "rugged".freeze
end
......
......@@ -80,7 +80,7 @@ changes.
The first example focuses on _how_ we fixed something, not on _what_ it fixes.
The rewritten version clearly describes the _end benefit_ to the user (fewer 500
errors), and _when_ (searching commits with ElasticSearch).
errors), and _when_ (searching commits with Elasticsearch).
Use your best judgement and try to put yourself in the mindset of someone
reading the compiled changelog. Does this entry add value? Does it offer context
......
......@@ -170,12 +170,6 @@ You can combine one or more of the following:
= link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
```
1. **Underlining a link.**
```haml
= link_to 'Help page', help_page_path('user/permissions'), class: 'underlined-link'
```
1. **Using links inline of some text.**
```haml
......
......@@ -175,7 +175,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of
### EC2 Instance
### ElasticSearch
### Elasticsearch
Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data.
......
......@@ -370,6 +370,9 @@ This also works for the asciidoctor `:stem: latexmath`. For details see the [asc
### Mermaid
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in
GitLab 10.3.
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#mermaid
......
# Redmine Service
To enable the Redmine integration in a project, navigate to the
1. To enable the Redmine integration in a project, navigate to the
[Integrations page](project_services.md#accessing-the-project-services), click
the **Redmine** service, and fill in the required details on the page as described
in the table below.
| Field | Description |
| ----- | ----------- |
| `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project |
| Field | Description |
| ----- | ----------- |
| `description` | A name for the issue tracker (to differentiate between instances, for example) |
| `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project |
Once you have configured and enabled Redmine:
- the **Issues** link on the GitLab project pages takes you to the appropriate
Once you have configured and enabled Redmine:
- the **Issues** link on the GitLab project pages takes you to the appropriate
Redmine issue index
- clicking **New issue** on the project dashboard creates a new Redmine issue
- clicking **New issue** on the project dashboard creates a new Redmine issue
As an example, below is a configuration for a project named gitlab-ci.
![Redmine configuration](img/redmine_configuration.png)
As an example, below is a configuration for a project named gitlab-ci.
2. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
![Redmine configuration](img/redmine_configuration.png)
![Issue configuration](img/issue_configuration.png)
## Referencing issues in Redmine
......
......@@ -3,21 +3,41 @@ require 'capybara-screenshot/spinach'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
Capybara.javascript_driver = :chrome
Capybara.register_driver :chrome do |app|
extra_args = []
extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
chromeOptions: {
'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args
# This enables access to logs with `page.driver.manage.get_log(:browser)`
loggingPrefs: {
browser: "ALL",
client: "ALL",
driver: "ALL",
server: "ALL"
}
)
Capybara::Selenium::Driver
.new(app, browser: :chrome, desired_capabilities: capabilities)
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument("window-size=1240,1400")
# Chrome won't work properly in a Docker container in sandbox mode
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
desired_capabilities: capabilities,
options: options
)
end
Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = false
......
......@@ -2,6 +2,8 @@ module API
module Helpers
module Pagination
def paginate(relation)
relation = add_default_order(relation)
relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
......@@ -45,6 +47,14 @@ module API
# Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max
end
def add_default_order(relation)
if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
relation = relation.order(:id)
end
relation
end
end
end
end
......@@ -82,6 +82,8 @@ module API
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
present paginate(users), with: entity
end
......
......@@ -1119,9 +1119,11 @@ module Gitlab
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git(args, env: {})
def run_git(args, env: {}, nice: false)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
circuit_breaker.perform do
popen([Gitlab.config.git.bin_path, *args], path, env)
popen(cmd, path, env)
end
end
......@@ -1201,6 +1203,12 @@ module Gitlab
end
end
def fsck
output, status = run_git(%W[--git-dir=#{path} fsck], nice: true)
raise GitError.new("Could not fsck repository:\n#{output}") unless status.zero?
end
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end
......@@ -1267,7 +1275,11 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695
def git_merged_branch_names(branch_names = [])
root_sha = find_branch(root_ref).target
return [] unless root_ref
root_sha = find_branch(root_ref)&.target
return [] unless root_sha
git_arguments =
%W[branch --merged #{root_sha}
......
......@@ -15,6 +15,7 @@ module Gitlab
Failing = Class.new(Inaccessible)
REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
REDIS_KNOWN_KEYS = "#{REDIS_KEY_PREFIX}known_keys_set".freeze
def self.redis
Gitlab::Redis::SharedState
......
......@@ -13,10 +13,8 @@ module Gitlab
delegate :last_failure, :failure_count, to: :failure_info
def self.reset_all!
pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*"
Gitlab::Git::Storage.redis.with do |redis|
all_storage_keys = redis.scan_each(match: pattern).to_a
all_storage_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
redis.del(*all_storage_keys) unless all_storage_keys.empty?
end
......@@ -135,23 +133,29 @@ module Gitlab
redis.hset(cache_key, :last_failure, last_failure.to_i)
redis.hincrby(cache_key, :failure_count, 1)
redis.expire(cache_key, failure_reset_time)
maintain_known_keys(redis)
end
end
end
def track_storage_accessible
return if no_failures?
@failure_info = FailureInfo.new(nil, 0)
Gitlab::Git::Storage.redis.with do |redis|
redis.pipelined do
redis.hset(cache_key, :last_failure, nil)
redis.hset(cache_key, :failure_count, 0)
maintain_known_keys(redis)
end
end
end
def maintain_known_keys(redis)
expire_time = Time.now.to_i + failure_reset_time
redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
end
def get_failure_info
last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
redis.hmget(cache_key, :last_failure, :failure_count)
......
......@@ -4,8 +4,8 @@ module Gitlab
class Health
attr_reader :storage_name, :info
def self.pattern_for_storage(storage_name)
"#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*"
def self.prefix_for_storage(storage_name)
"#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:"
end
def self.for_all_storages
......@@ -25,26 +25,15 @@ module Gitlab
private_class_method def self.all_keys_for_storages(storage_names, redis)
keys_per_storage = {}
all_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
redis.pipelined do
storage_names.each do |storage_name|
pattern = pattern_for_storage(storage_name)
matched_keys = redis.scan_each(match: pattern)
prefix = prefix_for_storage(storage_name)
keys_per_storage[storage_name] = matched_keys
end
keys_per_storage[storage_name] = all_keys.select { |key| key.starts_with?(prefix) }
end
# We need to make sure each lazy-loaded `Enumerator` for matched keys
# is loaded into an array.
#
# Otherwise it would be loaded in the second `Redis#pipelined` block
# within `.load_for_keys`. In this pipelined call, the active
# Redis-client changes again, so the values would not be available
# until the end of that pipelined-block.
keys_per_storage.each do |storage_name, key_future|
keys_per_storage[storage_name] = key_future.to_a
end
keys_per_storage
end
private_class_method def self.load_for_keys(keys_per_storage, redis)
......
......@@ -18,7 +18,7 @@ module Gitlab
def initialize(kubeclient)
@kubeclient = kubeclient
@namespace = Namespace.new(NAMESPACE, kubeclient)
@namespace = Gitlab::Kubernetes::Namespace.new(NAMESPACE, kubeclient)
end
def install(command)
......
......@@ -30,12 +30,16 @@ module Omnibus
private
def ee?
File.exist?('CHANGELOG-EE.md')
end
def env_params
{
"ref" => ENV["OMNIBUS_BRANCH"] || "master",
"variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"],
"variables[ALTERNATIVE_SOURCES]" => true,
"variables[ee]" => ENV["EE_PACKAGE"] || "false"
"variables[ee]" => ee? ? 'true' : 'false'
}
end
......
......@@ -205,7 +205,7 @@ describe MarkupHelper do
it "uses Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page")
expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", issuable_state_filter_enabled: true)
helper.render_wiki_content(@wiki)
end
......
/* eslint-disable space-before-function-paren, no-var */
import '~/behaviors/requires_input';
(function() {
describe('requiresInput', function() {
describe('requiresInput', () => {
let submitButton;
preloadFixtures('branches/new_branch.html.raw');
beforeEach(function() {
beforeEach(() => {
loadFixtures('branches/new_branch.html.raw');
this.submitButton = $('button[type="submit"]');
submitButton = $('button[type="submit"]');
});
it('disables submit when any field is required', function() {
it('disables submit when any field is required', () => {
$('.js-requires-input').requiresInput();
return expect(this.submitButton).toBeDisabled();
expect(submitButton).toBeDisabled();
});
it('enables submit when no field is required', function() {
it('enables submit when no field is required', () => {
$('*[required=required]').removeAttr('required');
$('.js-requires-input').requiresInput();
return expect(this.submitButton).not.toBeDisabled();
expect(submitButton).not.toBeDisabled();
});
it('enables submit when all required fields are pre-filled', function() {
it('enables submit when all required fields are pre-filled', () => {
$('*[required=required]').remove();
$('.js-requires-input').requiresInput();
return expect($('.submit')).not.toBeDisabled();
expect($('.submit')).not.toBeDisabled();
});
it('enables submit when all required fields receive input', function() {
it('enables submit when all required fields receive input', () => {
$('.js-requires-input').requiresInput();
$('#required1').val('input1').change();
expect(this.submitButton).toBeDisabled();
expect(submitButton).toBeDisabled();
$('#optional1').val('input1').change();
expect(this.submitButton).toBeDisabled();
expect(submitButton).toBeDisabled();
$('#required2').val('input2').change();
$('#required3').val('input3').change();
$('#required4').val('input4').change();
$('#required5').val('1').change();
return expect($('.submit')).not.toBeDisabled();
});
expect($('.submit')).not.toBeDisabled();
});
}).call(window);
});
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import Autosize from 'autosize';
import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue';
import { loggedOutIssueData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_comment_form component', () => {
......@@ -190,7 +190,7 @@ describe('issue_comment_form component', () => {
describe('user is not logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', null);
store.dispatch('setNoteableData', loggedOutIssueData);
store.dispatch('setNoteableData', loggedOutnoteableData);
store.dispatch('setNotesData', notesDataMock);
vm = mountComponent();
......
import Vue from 'vue';
import issueNotesApp from '~/notes/components/issue_notes_app.vue';
import service from '~/notes/services/issue_notes_service';
import service from '~/notes/services/notes_service';
import * as mockData from '../mock_data';
describe('issue_note_app', () => {
......
......@@ -271,7 +271,7 @@ export const discussionMock = {
individual_note: false,
};
export const loggedOutIssueData = {
export const loggedOutnoteableData = {
"id": 98,
"iid": 26,
"author_id": 1,
......
......@@ -92,6 +92,27 @@ describe API::Helpers::Pagination do
subject.paginate(resource)
end
end
context 'if order' do
it 'is not present it adds default order(:id) if no order is present' do
resource.order_values = []
paginated_relation = subject.paginate(resource)
expect(resource.order_values).to be_empty
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_ascending
expect(paginated_relation.order_values.first.expr.name).to eq :id
end
it 'is present it does not add anything' do
paginated_relation = subject.paginate(resource.order(created_at: :desc))
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_descending
expect(paginated_relation.order_values.first.expr.name).to eq :created_at
end
end
end
context 'when resource empty' do
......
......@@ -1210,6 +1210,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context 'when no root ref is available' do
it 'returns empty list' do
project = create(:project, :empty_repo)
names = project.repository.merged_branch_names(%w[feature])
expect(names).to be_empty
end
end
context 'when no branch names are specified' do
before do
repository.create_branch('identical', 'master')
......
......@@ -27,6 +27,7 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
def set_in_redis(name, value)
Gitlab::Git::Storage.redis.with do |redis|
redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
redis.hmset(cache_key, name, value)
end.first
end
......@@ -181,6 +182,24 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
expect(circuit_breaker.last_failure).to be_nil
end
it 'maintains known storage keys' do
Timecop.freeze do
# Insert an old key to expire
old_entry = Time.now.to_i - 3.days.to_i
Gitlab::Git::Storage.redis.with do |redis|
redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, old_entry, 'to_be_removed')
end
circuit_breaker.perform { '' }
known_keys = Gitlab::Git::Storage.redis.with do |redis|
redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
end
expect(known_keys).to contain_exactly(cache_key)
end
end
it 'only performs the accessibility check once' do
expect(Gitlab::Git::Storage::ForkedStorageCheck)
.to receive(:storage_available?).once.and_call_original
......
......@@ -6,6 +6,7 @@ describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, br
def set_in_redis(cache_key, value)
Gitlab::Git::Storage.redis.with do |redis|
redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
redis.hmset(cache_key, :failure_count, value)
end.first
end
......
......@@ -717,7 +717,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_subject("Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})")
is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})")
is_expected.to have_body_text(commit.short_id)
end
end
......@@ -827,7 +827,7 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject' do
is_expected.to have_subject "Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})"
is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
end
it 'contains a link to the commit' do
......
......@@ -142,6 +142,7 @@ describe Project do
it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
it { is_expected.to allow_value('').for(:ci_config_path) }
it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
it { is_expected.to validate_presence_of(:creator) }
......@@ -1713,8 +1714,8 @@ describe Project do
expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
end
it 'sets a string but removes all leading slashes and null characters' do
project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml")
it 'sets a string but removes all null characters' do
project.update!(ci_config_path: "f\0oo/\0/.gitlab_ci.yml")
expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
end
......
......@@ -77,4 +77,10 @@ describe BaseCountService, :use_clean_rails_memory_store_caching do
expect { service.cache_key }.to raise_error(NotImplementedError)
end
end
describe '#cache_options' do
it 'returns the default in options' do
expect(service.cache_options).to eq({ raw: false })
end
end
end
......@@ -22,6 +22,8 @@ describe Clusters::Applications::ScheduleInstallationService do
let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) }
it 'creates a new application' do
allow(ClusterInstallAppWorker).to receive(:perform_async)
expect { service.execute }.to change { application_class.count }.by(1)
end
......
......@@ -12,6 +12,8 @@ describe NotificationService, :mailer do
shared_examples 'notifications for new mentions' do
def send_notifications(*new_mentions)
mentionable.description = new_mentions.map(&:to_reference).join(' ')
notification.send(notification_method, mentionable, new_mentions, @u_disabled)
end
......@@ -20,13 +22,13 @@ describe NotificationService, :mailer do
should_not_email_anyone
end
it 'emails new mentions with a watch level higher than participant' do
send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global)
should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global)
it 'emails new mentions with a watch level higher than mention' do
send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned)
should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned)
end
it 'does not email new mentions with a watch level equal to or less than participant' do
send_notifications(@u_participating, @u_mentioned)
it 'does not email new mentions with a watch level equal to or less than mention' do
send_notifications(@u_disabled)
should_not_email_anyone
end
end
......@@ -509,6 +511,14 @@ describe NotificationService, :mailer do
should_not_email(issue.assignees.first)
end
it "emails any mentioned users with the mention level" do
issue.description = @u_mentioned.to_reference
notification.new_issue(issue, @u_disabled)
should_email(@u_mentioned)
end
it "emails the author if they've opted into notifications about their activity" do
issue.author.notified_of_own_activity = true
......@@ -900,6 +910,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
it "emails any mentioned users with the mention level" do
merge_request.description = @u_mentioned.to_reference
notification.new_merge_request(merge_request, @u_disabled)
should_email(@u_mentioned)
end
it "emails the author if they've opted into notifications about their activity" do
merge_request.author.notified_of_own_activity = true
......
......@@ -6,7 +6,6 @@ describe Projects::CountService do
describe '.query' do
it 'raises NotImplementedError' do
expect { service.relation_for_count }.to raise_error(NotImplementedError)
expect { described_class.query(project.id) }.to raise_error(NotImplementedError)
end
end
......
......@@ -7,21 +7,41 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
Capybara.javascript_driver = :chrome
Capybara.register_driver :chrome do |app|
extra_args = []
extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
chromeOptions: {
'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args
# This enables access to logs with `page.driver.manage.get_log(:browser)`
loggingPrefs: {
browser: "ALL",
client: "ALL",
driver: "ALL",
server: "ALL"
}
)
Capybara::Selenium::Driver
.new(app, browser: :chrome, desired_capabilities: capabilities)
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument("window-size=1240,1400")
# Chrome won't work properly in a Docker container in sandbox mode
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
desired_capabilities: capabilities,
options: options
)
end
Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
......
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