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' source 'https://rubygems.org'
gem 'rails', '4.2.8' gem 'rails', '4.2.10'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
......
...@@ -4,38 +4,38 @@ GEM ...@@ -4,38 +4,38 @@ GEM
RedCloth (4.3.2) RedCloth (4.3.2)
abstract_type (0.0.7) abstract_type (0.0.7)
ace-rails-ap (4.1.2) ace-rails-ap (4.1.2)
actionmailer (4.2.8) actionmailer (4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activejob (= 4.2.8) activejob (= 4.2.10)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.8) actionpack (4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.8) actionview (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (4.2.8) activejob (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.8) activemodel (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.8) activerecord (4.2.10)
activemodel (= 4.2.8) activemodel (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
arel (~> 6.0) arel (~> 6.0)
activerecord_sane_schema_dumper (0.2) activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5) rails (>= 4, < 5)
activesupport (4.2.8) activesupport (4.2.10)
i18n (~> 0.7) i18n (~> 0.7)
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
...@@ -325,8 +325,8 @@ GEM ...@@ -325,8 +325,8 @@ GEM
omniauth (~> 1.3) omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1) pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5) rubyntlm (~> 0.5)
globalid (0.3.7) globalid (0.4.1)
activesupport (>= 4.1.0) activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7) gollum-lib (4.2.7)
...@@ -427,7 +427,8 @@ GEM ...@@ -427,7 +427,8 @@ GEM
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.2) httpclient (2.8.2)
i18n (0.8.6) i18n (0.9.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
cause cause
...@@ -502,8 +503,8 @@ GEM ...@@ -502,8 +503,8 @@ GEM
railties (>= 4, < 5.2) railties (>= 4, < 5.2)
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.6.6) mail (2.7.0)
mime-types (>= 1.16, < 4) mini_mime (>= 0.1.1)
mail_room (0.9.1) mail_room (0.9.1)
memoist (0.16.0) memoist (0.16.0)
memoizable (0.4.2) memoizable (0.4.2)
...@@ -602,8 +603,8 @@ GEM ...@@ -602,8 +603,8 @@ GEM
parallel (1.12.0) parallel (1.12.0)
paranoia (2.3.1) paranoia (2.3.1)
activerecord (>= 4.0, < 5.2) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.2)
ast (~> 2.2) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
path_expander (1.0.1) path_expander (1.0.1)
...@@ -685,16 +686,16 @@ GEM ...@@ -685,16 +686,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.8) rails (4.2.10)
actionmailer (= 4.2.8) actionmailer (= 4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activejob (= 4.2.8) activejob (= 4.2.10)
activemodel (= 4.2.8) activemodel (= 4.2.10)
activerecord (= 4.2.8) activerecord (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.8) railties (= 4.2.10)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
...@@ -707,15 +708,15 @@ GEM ...@@ -707,15 +708,15 @@ GEM
rails-i18n (4.0.9) rails-i18n (4.0.9)
i18n (~> 0.7) i18n (~> 0.7)
railties (~> 4.0) railties (~> 4.0)
railties (4.2.8) railties (4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.18.0) raindrops (0.18.0)
rake (12.1.0) rake (12.3.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2) rbnacl (4.0.2)
...@@ -902,7 +903,7 @@ GEM ...@@ -902,7 +903,7 @@ GEM
sprockets (3.7.1) sprockets (3.7.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.0) sprockets-rails (3.2.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
...@@ -941,7 +942,7 @@ GEM ...@@ -941,7 +942,7 @@ GEM
truncato (0.7.10) truncato (0.7.10)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0) nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.3) tzinfo (1.2.4)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0) uber (0.1.0)
...@@ -1156,7 +1157,7 @@ DEPENDENCIES ...@@ -1156,7 +1157,7 @@ DEPENDENCIES
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rails (= 4.2.8) rails (= 4.2.10)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9) rails-i18n (~> 4.0.9)
rainbow (~> 2.2) rainbow (~> 2.2)
......
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue'; import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -8,6 +9,9 @@ import itemStats from './item_stats.vue'; ...@@ -8,6 +9,9 @@ import itemStats from './item_stats.vue';
import itemActions from './item_actions.vue'; import itemActions from './item_actions.vue';
export default { export default {
directives: {
tooltip,
},
components: { components: {
identicon, identicon,
itemCaret, itemCaret,
...@@ -112,10 +116,16 @@ export default { ...@@ -112,10 +116,16 @@ export default {
</a> </a>
</div> </div>
<div <div
class="title"> class="title namespace-title">
<a <a
v-tooltip
:href="group.relativePath" :href="group.relativePath"
class="no-expand">{{group.fullName}}</a> :title="group.fullName"
class="no-expand"
data-placement="top"
>
{{group.name}}
</a>
<span <span
v-if="group.permission" v-if="group.permission"
class="access-type" class="access-type"
......
...@@ -4,7 +4,7 @@ import Poll from '../../lib/utils/poll'; ...@@ -4,7 +4,7 @@ import Poll from '../../lib/utils/poll';
import * as types from './mutation_types'; import * as types from './mutation_types';
import * as utils from './utils'; import * as utils from './utils';
import * as constants from '../constants'; import * as constants from '../constants';
import service from '../services/issue_notes_service'; import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler'; import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
......
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
color: $brand-info; color: $brand-info;
} }
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; } .hint { font-style: italic; color: $hint-color; }
.light { color: $common-gray; } .light { color: $common-gray; }
......
...@@ -14,6 +14,5 @@ ...@@ -14,6 +14,5 @@
&:hover { &:hover {
background-color: $user-mention-bg-hover; background-color: $user-mention-bg-hover;
text-decoration: none;
} }
} }
...@@ -455,6 +455,12 @@ ul.indent-list { ...@@ -455,6 +455,12 @@ ul.indent-list {
} }
} }
.namespace-title {
.tooltip-inner {
max-width: 350px;
}
}
ul.group-list-tree { ul.group-list-tree {
li.group-row { li.group-row {
&.has-description { &.has-description {
......
...@@ -134,19 +134,22 @@ ...@@ -134,19 +134,22 @@
} }
.select2-search { .select2-search {
padding: 15px 15px 5px; padding: $grid-size;
.select2-drop-auto-width & { .select2-drop-auto-width & {
padding: 15px 15px 5px; padding: $grid-size;
} }
input { input {
padding: 2px 25px 2px 5px; padding: $grid-size;
background: $white-light image-url('select2.png'); background: $white-light image-url('select2.png');
background-clip: content-box;
background-origin: content-box;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0 bottom 6px; background-position: right 0 bottom 0 !important;
border: 1px solid $input-border; border: 1px solid $input-border;
border-radius: $border-radius-default; border-radius: $border-radius-default;
line-height: 16px;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus { &:focus {
...@@ -156,11 +159,16 @@ ...@@ -156,11 +159,16 @@
&.select2-active { &.select2-active {
background-color: $white-light; background-color: $white-light;
background-image: image-url('select2-spinner.gif') !important; background-image: image-url('select2-spinner.gif') !important;
background-origin: content-box;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 5px center !important; background-position: right 6px center !important;
background-size: 16px 16px !important; background-size: 16px 16px !important;
} }
} }
+ .select2-results {
padding-top: 0;
}
} }
.select2-results { .select2-results {
......
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
.ref-name { .ref-name {
font-size: 12px; font-size: 12px;
&:hover {
text-decoration: underline;
color: $gl-text-color;
}
} }
} }
......
...@@ -110,6 +110,10 @@ ...@@ -110,6 +110,10 @@
padding: 6px 10px; padding: 6px 10px;
border-radius: $label-border-radius; border-radius: $label-border-radius;
} }
&:hover .color-label {
text-decoration: underline;
}
} }
&.has-labels { &.has-labels {
...@@ -174,6 +178,14 @@ ...@@ -174,6 +178,14 @@
color: $gray-darkest; color: $gray-darkest;
} }
} }
&.assignee {
.author_link:hover {
.author {
text-decoration: underline;
}
}
}
} }
.block-first { .block-first {
...@@ -469,7 +481,6 @@ ...@@ -469,7 +481,6 @@
a:not(.btn-retry) { a:not(.btn-retry) {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
text-decoration: none;
.avatar { .avatar {
border-color: rgba($avatar-border, .2); border-color: rgba($avatar-border, .2);
......
...@@ -208,7 +208,6 @@ ul.notes { ...@@ -208,7 +208,6 @@ ul.notes {
a { a {
color: $gl-link-color; color: $gl-link-color;
text-decoration: none;
} }
p { p {
...@@ -395,6 +394,10 @@ ul.notes { ...@@ -395,6 +394,10 @@ ul.notes {
&:focus, &:focus,
&:hover { &:hover {
text-decoration: none; text-decoration: none;
.note-header-author-name {
text-decoration: underline;
}
} }
} }
...@@ -461,6 +464,10 @@ ul.notes { ...@@ -461,6 +464,10 @@ ul.notes {
.system-note-message { .system-note-message {
white-space: normal; white-space: normal;
} }
a:hover {
text-decoration: underline;
}
} }
/** /**
......
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
.profile-link-holder { .profile-link-holder {
display: inline; display: inline;
a { a:not(.text-link) {
text-decoration: none; text-decoration: none;
} }
} }
......
...@@ -818,6 +818,7 @@ a.deploy-project-label { ...@@ -818,6 +818,7 @@ a.deploy-project-label {
&:hover, &:hover,
&:focus { &:focus {
color: $gl-text-color; color: $gl-text-color;
text-decoration: underline;
} }
} }
} }
......
...@@ -124,7 +124,11 @@ ...@@ -124,7 +124,11 @@
&:hover, &:hover,
&.active { &.active {
color: $black; text-decoration: none;
span {
text-decoration: underline;
}
} }
} }
......
...@@ -26,7 +26,7 @@ class UsersFinder ...@@ -26,7 +26,7 @@ class UsersFinder
end end
def execute def execute
users = User.all users = User.all.order_id_desc
users = by_username(users) users = by_username(users)
users = by_search(users) users = by_search(users)
users = by_blocked(users) users = by_blocked(users)
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do 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
end end
...@@ -77,7 +77,7 @@ module CommitsHelper ...@@ -77,7 +77,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link # Returns a link formatted as a commit tag link
def commit_tag_link(url, text) def commit_tag_link(url, text)
link_to(url, class: 'label label-gray ref-name') do link_to(url, class: 'label label-gray ref-name') do
icon('tag') + " #{text}" icon('tag', class: 'append-right-5') + "#{text}"
end end
end end
......
...@@ -113,7 +113,13 @@ module MarkupHelper ...@@ -113,7 +113,13 @@ module MarkupHelper
text = wiki_page.content text = wiki_page.content
return '' unless text.present? 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 = html =
case wiki_page.format case wiki_page.format
......
...@@ -239,8 +239,8 @@ class Project < ActiveRecord::Base ...@@ -239,8 +239,8 @@ class Project < ActiveRecord::Base
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path, validates :ci_config_path,
format: { without: /\.{2}/, format: { without: /(\.{2}|\A\/)/,
message: 'cannot include directory traversal.' }, message: 'cannot include leading slash or directory traversal.' },
length: { maximum: 255 }, length: { maximum: 255 },
allow_blank: true allow_blank: true
validates :name, validates :name,
...@@ -606,7 +606,7 @@ class Project < ActiveRecord::Base ...@@ -606,7 +606,7 @@ class Project < ActiveRecord::Base
def ci_config_path=(value) def ci_config_path=(value)
# Strip all leading slashes so that //foo -> foo # Strip all leading slashes so that //foo -> foo
super(value&.sub(%r{\A/+}, '')&.delete("\0")) super(value&.delete("\0"))
end end
def import_url=(value) def import_url=(value)
......
...@@ -263,7 +263,7 @@ class Repository ...@@ -263,7 +263,7 @@ class Repository
end end
def diverging_commit_counts(branch) 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 cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather # Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes # than SHA-1 hashes
......
...@@ -506,7 +506,11 @@ class User < ActiveRecord::Base ...@@ -506,7 +506,11 @@ class User < ActiveRecord::Base
end end
def two_factor_u2f_enabled? def two_factor_u2f_enabled?
u2f_registrations.exists? if u2f_registrations.loaded?
u2f_registrations.any?
else
u2f_registrations.exists?
end
end end
def namespace_uniq def namespace_uniq
......
...@@ -98,6 +98,12 @@ module NotificationRecipientService ...@@ -98,6 +98,12 @@ module NotificationRecipientService
self << [target.participants(user), :participating] self << [target.participants(user), :participating]
end 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 # Get project/group users with CUSTOM notification level
def add_custom_notifications def add_custom_notifications
user_ids = [] user_ids = []
...@@ -227,6 +233,11 @@ module NotificationRecipientService ...@@ -227,6 +233,11 @@ module NotificationRecipientService
add_subscribed_users add_subscribed_users
if [:new_issue, :new_merge_request].include?(custom_action) 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 add_labels_subscribers
end end
end end
...@@ -263,7 +274,7 @@ module NotificationRecipientService ...@@ -263,7 +274,7 @@ module NotificationRecipientService
def build! def build!
# Add all users participating in the thread (author, assignee, comment authors) # Add all users participating in the thread (author, assignee, comment authors)
add_participants(note.author) add_participants(note.author)
self << [note.mentioned_users, :mention] add_mentions(note.author, target: note)
unless note.for_personal_snippet? unless note.for_personal_snippet?
# Merge project watchers # Merge project watchers
......
...@@ -282,7 +282,7 @@ ...@@ -282,7 +282,7 @@
= render 'projects/settings/ee/nav' = render 'projects/settings/ee/nav'
- else - 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 = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
.nav-icon-container .nav-icon-container
= sprite_icon('users') = sprite_icon('users')
......
...@@ -19,5 +19,5 @@ ...@@ -19,5 +19,5 @@
distributed with computer software, forming part of its documentation. distributed with computer software, forming part of its documentation.
%p %p
We recommend you to 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. file to the repository and GitLab will render it here instead of this message.
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
%tr.tree-item{ 'data-link' => path_to_directory } %tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('folder', '755', directory.name) = tree_icon('folder', '755', directory.name)
= link_to path_to_directory do = link_to path_to_directory, class: 'str-truncated' do
%span.str-truncated= directory.name %span= directory.name
%td %td
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('file', blob.mode, blob.name) = tree_icon('file', blob.mode, blob.name)
- if external_link - 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 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') = icon('external-link', class: 'js-artifact-tree-external-icon')
- else - else
= link_to path_to_file do = link_to path_to_file, class: 'str-truncated' do
%span.str-truncated= blob.name %span= blob.name
%td %td
= number_to_human_size(blob.size, precision: 2) = number_to_human_size(blob.size, precision: 2)
...@@ -8,8 +8,7 @@ ...@@ -8,8 +8,7 @@
%li{ class: "js-branch-#{branch.name}" } %li{ class: "js-branch-#{branch.name}" }
%div %div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork') = icon('code-fork', class: 'append-right-5') + "#{branch.name}"
= branch.name
&nbsp; &nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
......
...@@ -48,6 +48,6 @@ ...@@ -48,6 +48,6 @@
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: 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")) = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
...@@ -14,12 +14,12 @@ ...@@ -14,12 +14,12 @@
%p %p
Otherwise you can start with adding a Otherwise you can start with adding a
= succeed ',' do = 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 a
= succeed ',' do = 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 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. to this project.
%p %p
You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. 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 @@ ...@@ -16,7 +16,7 @@
%li prevent pushes from everybody except Masters %li prevent pushes from everybody except Masters
%li prevent <strong>anyone</strong> from force pushing to the branch %li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting 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 - if can? current_user, :admin_project, @project
= content_for :create_protected_branch = content_for :create_protected_branch
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%li Prevent <strong>anyone</strong> from updating the tag %li Prevent <strong>anyone</strong> from updating the tag
%li Prevent <strong>anyone</strong> from deleting 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 - if can? current_user, :admin_project, @project
= yield :create_protected_tag = yield :create_protected_tag
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name) = tree_icon(type, blob_item.mode, blob_item.name)
- file_name = 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 = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span.str-truncated= file_name %span= file_name
%td.hidden-xs.tree-commit %td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right %td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name) = tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(@path, tree_item) - path = flatten_tree(@path, tree_item)
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), class: 'str-truncated', title: path do
%span.str-truncated= path %span= path
%td.hidden-xs.tree-commit %td.hidden-xs.tree-commit
%td.tree-time-ago.text-right %td.tree-time-ago.text-right
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
- git_access_url = project_wikis_git_access_path(@project) - git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do = icon('cloud-download', class: 'append-right-5')
= icon('cloud-download') %span= _("Clone repository")
= _("Clone repository")
.blocks-container .blocks-container
.block.block-first .block.block-first
......
...@@ -38,9 +38,9 @@ ...@@ -38,9 +38,9 @@
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'} = 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 .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 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 view open issues
- if current_user - if current_user
......
...@@ -31,8 +31,7 @@ ...@@ -31,8 +31,7 @@
.note-header .note-header
.note-header-info .note-header-info
%a{ href: user_path(note.author) } %a{ href: user_path(note.author) }
%span.note-header-author-name %span.note-header-author-name= sanitize(note.author.name)
= sanitize(note.author.name)
%span.note-headline-light %span.note-headline-light
= note.author.to_reference = note.author.to_reference
%span.note-headline-light %span.note-headline-light
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
.cover-desc .cover-desc
- unless @user.public_email.blank? - unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider .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? - unless @user.skype.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do = link_to "skype:#{@user.skype}", title: "Skype" do
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
= icon('twitter-square') = icon('twitter-square')
- unless @user.website_url.blank? - unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider .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? - unless @user.location.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= icon('map-marker') = icon('map-marker')
......
...@@ -32,16 +32,14 @@ module RepositoryCheck ...@@ -32,16 +32,14 @@ module RepositoryCheck
end end
def git_fsck(repository) def git_fsck(repository)
path = repository.path_to_repo return false unless repository.exists?
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
if status.zero? repository.raw_repository.fsck
true
else true
Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") rescue Gitlab::Git::Repository::GitError => e
false Gitlab::RepositoryCheckLogger.error(e.message)
end false
end end
def has_pushes?(project) def has_pushes?(project)
......
---
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 module Gollum
GIT_ADAPTER = "rugged".freeze GIT_ADAPTER = "rugged".freeze
end end
......
...@@ -80,7 +80,7 @@ changes. ...@@ -80,7 +80,7 @@ changes.
The first example focuses on _how_ we fixed something, not on _what_ it fixes. 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 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 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 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: ...@@ -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' = 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.** 1. **Using links inline of some text.**
```haml ```haml
......
...@@ -175,7 +175,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of ...@@ -175,7 +175,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of
### EC2 Instance ### 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. 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.
......
...@@ -41,7 +41,7 @@ Line-breaks, or softreturns, are rendered if you end a line with two or more spa ...@@ -41,7 +41,7 @@ Line-breaks, or softreturns, are rendered if you end a line with two or more spa
Sugar is sweet Sugar is sweet
Roses are red Roses are red
Violets are blue Violets are blue
Sugar is sweet Sugar is sweet
...@@ -370,14 +370,17 @@ This also works for the asciidoctor `:stem: latexmath`. For details see the [asc ...@@ -370,14 +370,17 @@ This also works for the asciidoctor `:stem: latexmath`. For details see the [asc
### Mermaid ### Mermaid
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in
GitLab 10.3.
> If this is not rendered correctly, see > If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#mermaid https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#mermaid
It is possible to generate diagrams and flowcharts from text using [Mermaid][mermaid]. It is possible to generate diagrams and flowcharts from text using [Mermaid][mermaid].
In order to generate a diagram or flowchart, you should write your text inside the `mermaid` block. In order to generate a diagram or flowchart, you should write your text inside the `mermaid` block.
Example: Example:
```mermaid ```mermaid
graph TD; graph TD;
...@@ -385,7 +388,7 @@ Example: ...@@ -385,7 +388,7 @@ Example:
A-->C; A-->C;
B-->D; B-->D;
C-->D; C-->D;
``` ```
Becomes: Becomes:
...@@ -395,7 +398,7 @@ graph TD; ...@@ -395,7 +398,7 @@ graph TD;
A-->C; A-->C;
B-->D; B-->D;
C-->D; C-->D;
``` ```
For details see the [Mermaid official page][mermaid]. For details see the [Mermaid official page][mermaid].
...@@ -697,7 +700,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa ...@@ -697,7 +700,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also a separate paragraph, but... This line is also a separate paragraph, but...
This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*. This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*.
This line is also a separate paragraph, and... This line is also a separate paragraph, and...
This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*) This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*)
spaces. spaces.
...@@ -710,7 +713,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa ...@@ -710,7 +713,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also a separate paragraph, but... This line is also a separate paragraph, but...
This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*. This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*.
This line is also a separate paragraph, and... This line is also a separate paragraph, and...
This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*) This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*)
spaces. spaces.
......
# Redmine Service # 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 [Integrations page](project_services.md#accessing-the-project-services), click
the **Redmine** service, and fill in the required details on the page as described the **Redmine** service, and fill in the required details on the page as described
in the table below. in the table below.
| Field | Description | | Field | Description |
| ----- | ----------- | | ----- | ----------- |
| `description` | A name for the issue tracker (to differentiate between instances, for example) | | `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 | | `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. | | `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 | | `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: 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
- the **Issues** link on the GitLab project pages takes you to the appropriate As an example, below is a configuration for a project named gitlab-ci.
Redmine issue index
- 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)
![Redmine configuration](img/redmine_configuration.png) 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.
![Issue configuration](img/issue_configuration.png)
## Referencing issues in Redmine ## Referencing issues in Redmine
......
...@@ -3,21 +3,41 @@ require 'capybara-screenshot/spinach' ...@@ -3,21 +3,41 @@ require 'capybara-screenshot/spinach'
# Give CI some extra time # Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30 timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
Capybara.javascript_driver = :chrome
Capybara.register_driver :chrome do |app| 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( capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
chromeOptions: { # This enables access to logs with `page.driver.manage.get_log(:browser)`
'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args loggingPrefs: {
browser: "ALL",
client: "ALL",
driver: "ALL",
server: "ALL"
} }
) )
Capybara::Selenium::Driver options = Selenium::WebDriver::Chrome::Options.new
.new(app, browser: :chrome, desired_capabilities: capabilities) 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 end
Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = false Capybara.ignore_hidden_elements = false
......
...@@ -2,6 +2,8 @@ module API ...@@ -2,6 +2,8 @@ module API
module Helpers module Helpers
module Pagination module Pagination
def paginate(relation) def paginate(relation)
relation = add_default_order(relation)
relation.page(params[:page]).per(params[:per_page]).tap do |data| relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data) add_pagination_headers(data)
end end
...@@ -45,6 +47,14 @@ module API ...@@ -45,6 +47,14 @@ module API
# Ensure there is in total at least 1 page # Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max [paginated_data.total_pages, 1].max
end 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 end
end end
...@@ -82,6 +82,8 @@ module API ...@@ -82,6 +82,8 @@ module API
forbidden!("Not authorized to access /api/v4/users") unless authorized forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
present paginate(users), with: entity present paginate(users), with: entity
end end
......
...@@ -1119,9 +1119,11 @@ module Gitlab ...@@ -1119,9 +1119,11 @@ module Gitlab
end end
# Refactoring aid; allows us to copy code from app/models/repository.rb # 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 circuit_breaker.perform do
popen([Gitlab.config.git.bin_path, *args], path, env) popen(cmd, path, env)
end end
end end
...@@ -1201,6 +1203,12 @@ module Gitlab ...@@ -1201,6 +1203,12 @@ module Gitlab
end end
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 def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end end
...@@ -1267,7 +1275,11 @@ module Gitlab ...@@ -1267,7 +1275,11 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695
def git_merged_branch_names(branch_names = []) 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 = git_arguments =
%W[branch --merged #{root_sha} %W[branch --merged #{root_sha}
......
...@@ -15,6 +15,7 @@ module Gitlab ...@@ -15,6 +15,7 @@ module Gitlab
Failing = Class.new(Inaccessible) Failing = Class.new(Inaccessible)
REDIS_KEY_PREFIX = 'storage_accessible:'.freeze REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
REDIS_KNOWN_KEYS = "#{REDIS_KEY_PREFIX}known_keys_set".freeze
def self.redis def self.redis
Gitlab::Redis::SharedState Gitlab::Redis::SharedState
......
...@@ -13,10 +13,8 @@ module Gitlab ...@@ -13,10 +13,8 @@ module Gitlab
delegate :last_failure, :failure_count, to: :failure_info delegate :last_failure, :failure_count, to: :failure_info
def self.reset_all! def self.reset_all!
pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*"
Gitlab::Git::Storage.redis.with do |redis| 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? redis.del(*all_storage_keys) unless all_storage_keys.empty?
end end
...@@ -135,23 +133,29 @@ module Gitlab ...@@ -135,23 +133,29 @@ module Gitlab
redis.hset(cache_key, :last_failure, last_failure.to_i) redis.hset(cache_key, :last_failure, last_failure.to_i)
redis.hincrby(cache_key, :failure_count, 1) redis.hincrby(cache_key, :failure_count, 1)
redis.expire(cache_key, failure_reset_time) redis.expire(cache_key, failure_reset_time)
maintain_known_keys(redis)
end end
end end
end end
def track_storage_accessible def track_storage_accessible
return if no_failures?
@failure_info = FailureInfo.new(nil, 0) @failure_info = FailureInfo.new(nil, 0)
Gitlab::Git::Storage.redis.with do |redis| Gitlab::Git::Storage.redis.with do |redis|
redis.pipelined do redis.pipelined do
redis.hset(cache_key, :last_failure, nil) redis.hset(cache_key, :last_failure, nil)
redis.hset(cache_key, :failure_count, 0) redis.hset(cache_key, :failure_count, 0)
maintain_known_keys(redis)
end end
end 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 def get_failure_info
last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis| last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
redis.hmget(cache_key, :last_failure, :failure_count) redis.hmget(cache_key, :last_failure, :failure_count)
......
...@@ -4,8 +4,8 @@ module Gitlab ...@@ -4,8 +4,8 @@ module Gitlab
class Health class Health
attr_reader :storage_name, :info attr_reader :storage_name, :info
def self.pattern_for_storage(storage_name) def self.prefix_for_storage(storage_name)
"#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*" "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:"
end end
def self.for_all_storages def self.for_all_storages
...@@ -25,26 +25,15 @@ module Gitlab ...@@ -25,26 +25,15 @@ module Gitlab
private_class_method def self.all_keys_for_storages(storage_names, redis) private_class_method def self.all_keys_for_storages(storage_names, redis)
keys_per_storage = {} keys_per_storage = {}
all_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
redis.pipelined do storage_names.each do |storage_name|
storage_names.each do |storage_name| prefix = prefix_for_storage(storage_name)
pattern = pattern_for_storage(storage_name)
matched_keys = redis.scan_each(match: pattern)
keys_per_storage[storage_name] = matched_keys keys_per_storage[storage_name] = all_keys.select { |key| key.starts_with?(prefix) }
end
end end
# We need to make sure each lazy-loaded `Enumerator` for matched keys keys_per_storage
# 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
end end
private_class_method def self.load_for_keys(keys_per_storage, redis) private_class_method def self.load_for_keys(keys_per_storage, redis)
......
...@@ -18,7 +18,7 @@ module Gitlab ...@@ -18,7 +18,7 @@ module Gitlab
def initialize(kubeclient) def initialize(kubeclient)
@kubeclient = kubeclient @kubeclient = kubeclient
@namespace = Namespace.new(NAMESPACE, kubeclient) @namespace = Gitlab::Kubernetes::Namespace.new(NAMESPACE, kubeclient)
end end
def install(command) def install(command)
......
...@@ -30,12 +30,16 @@ module Omnibus ...@@ -30,12 +30,16 @@ module Omnibus
private private
def ee?
File.exist?('CHANGELOG-EE.md')
end
def env_params def env_params
{ {
"ref" => ENV["OMNIBUS_BRANCH"] || "master", "ref" => ENV["OMNIBUS_BRANCH"] || "master",
"variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"], "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"],
"variables[ALTERNATIVE_SOURCES]" => true, "variables[ALTERNATIVE_SOURCES]" => true,
"variables[ee]" => ENV["EE_PACKAGE"] || "false" "variables[ee]" => ee? ? 'true' : 'false'
} }
end end
......
...@@ -14,7 +14,7 @@ describe 'Discussion Lock', :js do ...@@ -14,7 +14,7 @@ describe 'Discussion Lock', :js do
project.add_developer(user) project.add_developer(user)
end end
context 'when the discussion is unlocked' do context 'when the discussion is unlocked' do
it 'the user can lock the issue' do it 'the user can lock the issue' do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
......
...@@ -205,7 +205,7 @@ describe MarkupHelper do ...@@ -205,7 +205,7 @@ describe MarkupHelper do
it "uses Wiki pipeline for markdown files" do it "uses Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown) 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) helper.render_wiki_content(@wiki)
end end
......
/* eslint-disable space-before-function-paren, no-var */
import '~/behaviors/requires_input'; import '~/behaviors/requires_input';
(function() { describe('requiresInput', () => {
describe('requiresInput', function() { let submitButton;
preloadFixtures('branches/new_branch.html.raw'); preloadFixtures('branches/new_branch.html.raw');
beforeEach(function() {
loadFixtures('branches/new_branch.html.raw'); beforeEach(() => {
this.submitButton = $('button[type="submit"]'); loadFixtures('branches/new_branch.html.raw');
}); submitButton = $('button[type="submit"]');
it('disables submit when any field is required', function() { });
$('.js-requires-input').requiresInput();
return expect(this.submitButton).toBeDisabled(); it('disables submit when any field is required', () => {
}); $('.js-requires-input').requiresInput();
it('enables submit when no field is required', function() { expect(submitButton).toBeDisabled();
$('*[required=required]').removeAttr('required'); });
$('.js-requires-input').requiresInput();
return expect(this.submitButton).not.toBeDisabled(); it('enables submit when no field is required', () => {
}); $('*[required=required]').removeAttr('required');
it('enables submit when all required fields are pre-filled', function() { $('.js-requires-input').requiresInput();
$('*[required=required]').remove(); expect(submitButton).not.toBeDisabled();
$('.js-requires-input').requiresInput(); });
return expect($('.submit')).not.toBeDisabled();
}); it('enables submit when all required fields are pre-filled', () => {
it('enables submit when all required fields receive input', function() { $('*[required=required]').remove();
$('.js-requires-input').requiresInput(); $('.js-requires-input').requiresInput();
$('#required1').val('input1').change(); expect($('.submit')).not.toBeDisabled();
expect(this.submitButton).toBeDisabled(); });
$('#optional1').val('input1').change();
expect(this.submitButton).toBeDisabled(); it('enables submit when all required fields receive input', () => {
$('#required2').val('input2').change(); $('.js-requires-input').requiresInput();
$('#required3').val('input3').change(); $('#required1').val('input1').change();
$('#required4').val('input4').change(); expect(submitButton).toBeDisabled();
$('#required5').val('1').change();
return expect($('.submit')).not.toBeDisabled(); $('#optional1').val('input1').change();
}); expect(submitButton).toBeDisabled();
$('#required2').val('input2').change();
$('#required3').val('input3').change();
$('#required4').val('input4').change();
$('#required5').val('1').change();
expect($('.submit')).not.toBeDisabled();
}); });
}).call(window); });
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import Autosize from 'autosize'; import Autosize from 'autosize';
import store from '~/notes/stores'; import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue'; 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'; import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_comment_form component', () => { describe('issue_comment_form component', () => {
...@@ -190,7 +190,7 @@ describe('issue_comment_form component', () => { ...@@ -190,7 +190,7 @@ describe('issue_comment_form component', () => {
describe('user is not logged in', () => { describe('user is not logged in', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setUserData', null); store.dispatch('setUserData', null);
store.dispatch('setNoteableData', loggedOutIssueData); store.dispatch('setNoteableData', loggedOutnoteableData);
store.dispatch('setNotesData', notesDataMock); store.dispatch('setNotesData', notesDataMock);
vm = mountComponent(); vm = mountComponent();
......
import Vue from 'vue'; import Vue from 'vue';
import issueNotesApp from '~/notes/components/issue_notes_app.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'; import * as mockData from '../mock_data';
describe('issue_note_app', () => { describe('issue_note_app', () => {
......
...@@ -271,7 +271,7 @@ export const discussionMock = { ...@@ -271,7 +271,7 @@ export const discussionMock = {
individual_note: false, individual_note: false,
}; };
export const loggedOutIssueData = { export const loggedOutnoteableData = {
"id": 98, "id": 98,
"iid": 26, "iid": 26,
"author_id": 1, "author_id": 1,
......
...@@ -92,6 +92,27 @@ describe API::Helpers::Pagination do ...@@ -92,6 +92,27 @@ describe API::Helpers::Pagination do
subject.paginate(resource) subject.paginate(resource)
end end
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 end
context 'when resource empty' do context 'when resource empty' do
......
...@@ -1210,6 +1210,16 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1210,6 +1210,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
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 context 'when no branch names are specified' do
before do before do
repository.create_branch('identical', 'master') repository.create_branch('identical', 'master')
......
...@@ -27,6 +27,7 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: ...@@ -27,6 +27,7 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
def set_in_redis(name, value) def set_in_redis(name, value)
Gitlab::Git::Storage.redis.with do |redis| Gitlab::Git::Storage.redis.with do |redis|
redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
redis.hmset(cache_key, name, value) redis.hmset(cache_key, name, value)
end.first end.first
end end
...@@ -181,6 +182,24 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: ...@@ -181,6 +182,24 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
expect(circuit_breaker.last_failure).to be_nil expect(circuit_breaker.last_failure).to be_nil
end 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 it 'only performs the accessibility check once' do
expect(Gitlab::Git::Storage::ForkedStorageCheck) expect(Gitlab::Git::Storage::ForkedStorageCheck)
.to receive(:storage_available?).once.and_call_original .to receive(:storage_available?).once.and_call_original
......
...@@ -6,6 +6,7 @@ describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, br ...@@ -6,6 +6,7 @@ describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, br
def set_in_redis(cache_key, value) def set_in_redis(cache_key, value)
Gitlab::Git::Storage.redis.with do |redis| 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) redis.hmset(cache_key, :failure_count, value)
end.first end.first
end end
......
...@@ -717,7 +717,7 @@ describe Notify do ...@@ -717,7 +717,7 @@ describe Notify do
it 'has the correct subject and body' do it 'has the correct subject and body' do
aggregate_failures 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) is_expected.to have_body_text(commit.short_id)
end end
end end
...@@ -827,7 +827,7 @@ describe Notify do ...@@ -827,7 +827,7 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link' it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject' do 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 end
it 'contains a link to the commit' do it 'contains a link to the commit' do
......
...@@ -142,6 +142,7 @@ describe Project 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 validate_length_of(:ci_config_path).is_at_most(255) }
it { is_expected.to allow_value('').for(:ci_config_path) } 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.not_to allow_value('/test/foo').for(:ci_config_path) }
it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:creator) }
...@@ -1713,8 +1714,8 @@ describe Project do ...@@ -1713,8 +1714,8 @@ describe Project do
expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml') expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
end end
it 'sets a string but removes all leading slashes and null characters' do it 'sets a string but removes all null characters' do
project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml") project.update!(ci_config_path: "f\0oo/\0/.gitlab_ci.yml")
expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml') expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
end end
......
...@@ -77,4 +77,10 @@ describe BaseCountService, :use_clean_rails_memory_store_caching do ...@@ -77,4 +77,10 @@ describe BaseCountService, :use_clean_rails_memory_store_caching do
expect { service.cache_key }.to raise_error(NotImplementedError) expect { service.cache_key }.to raise_error(NotImplementedError)
end end
end end
describe '#cache_options' do
it 'returns the default in options' do
expect(service.cache_options).to eq({ raw: false })
end
end
end end
...@@ -22,6 +22,8 @@ describe Clusters::Applications::ScheduleInstallationService do ...@@ -22,6 +22,8 @@ describe Clusters::Applications::ScheduleInstallationService do
let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) } let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) }
it 'creates a new application' do it 'creates a new application' do
allow(ClusterInstallAppWorker).to receive(:perform_async)
expect { service.execute }.to change { application_class.count }.by(1) expect { service.execute }.to change { application_class.count }.by(1)
end end
......
...@@ -12,6 +12,8 @@ describe NotificationService, :mailer do ...@@ -12,6 +12,8 @@ describe NotificationService, :mailer do
shared_examples 'notifications for new mentions' do shared_examples 'notifications for new mentions' do
def send_notifications(*new_mentions) def send_notifications(*new_mentions)
mentionable.description = new_mentions.map(&:to_reference).join(' ')
notification.send(notification_method, mentionable, new_mentions, @u_disabled) notification.send(notification_method, mentionable, new_mentions, @u_disabled)
end end
...@@ -20,13 +22,13 @@ describe NotificationService, :mailer do ...@@ -20,13 +22,13 @@ describe NotificationService, :mailer do
should_not_email_anyone should_not_email_anyone
end end
it 'emails new mentions with a watch level higher than participant' do it 'emails new mentions with a watch level higher than mention' do
send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global) send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned)
should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global) should_only_email(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned)
end end
it 'does not email new mentions with a watch level equal to or less than participant' do it 'does not email new mentions with a watch level equal to or less than mention' do
send_notifications(@u_participating, @u_mentioned) send_notifications(@u_disabled)
should_not_email_anyone should_not_email_anyone
end end
end end
...@@ -509,6 +511,14 @@ describe NotificationService, :mailer do ...@@ -509,6 +511,14 @@ describe NotificationService, :mailer do
should_not_email(issue.assignees.first) should_not_email(issue.assignees.first)
end 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 it "emails the author if they've opted into notifications about their activity" do
issue.author.notified_of_own_activity = true issue.author.notified_of_own_activity = true
...@@ -900,6 +910,14 @@ describe NotificationService, :mailer do ...@@ -900,6 +910,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant) should_not_email(@u_lazy_participant)
end 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 it "emails the author if they've opted into notifications about their activity" do
merge_request.author.notified_of_own_activity = true merge_request.author.notified_of_own_activity = true
......
...@@ -6,7 +6,6 @@ describe Projects::CountService do ...@@ -6,7 +6,6 @@ describe Projects::CountService do
describe '.query' do describe '.query' do
it 'raises NotImplementedError' do it 'raises NotImplementedError' do
expect { service.relation_for_count }.to raise_error(NotImplementedError)
expect { described_class.query(project.id) }.to raise_error(NotImplementedError) expect { described_class.query(project.id) }.to raise_error(NotImplementedError)
end end
end end
......
...@@ -7,21 +7,41 @@ require 'selenium-webdriver' ...@@ -7,21 +7,41 @@ require 'selenium-webdriver'
# Give CI some extra time # Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30 timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
Capybara.javascript_driver = :chrome
Capybara.register_driver :chrome do |app| 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( capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
chromeOptions: { # This enables access to logs with `page.driver.manage.get_log(:browser)`
'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args loggingPrefs: {
browser: "ALL",
client: "ALL",
driver: "ALL",
server: "ALL"
} }
) )
Capybara::Selenium::Driver options = Selenium::WebDriver::Chrome::Options.new
.new(app, browser: :chrome, desired_capabilities: capabilities) 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 end
Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true 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