Commit d537d251 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee' into 'master'

CE Upstream - Monday

Closes gitaly#524

See merge request gitlab-org/gitlab-ee!3007
parents 46a1af50 511f5570
...@@ -24,6 +24,9 @@ const categoryLabelMap = { ...@@ -24,6 +24,9 @@ const categoryLabelMap = {
flags: 'Flags', flags: 'Flags',
}; };
const IS_VISIBLE = 'is-visible';
const IS_RENDERED = 'is-rendered';
class AwardsHandler { class AwardsHandler {
constructor(emoji) { constructor(emoji) {
this.emoji = emoji; this.emoji = emoji;
...@@ -51,7 +54,7 @@ class AwardsHandler { ...@@ -51,7 +54,7 @@ class AwardsHandler {
if (!$target.closest('.emoji-menu').length) { if (!$target.closest('.emoji-menu').length) {
if ($('.emoji-menu').is(':visible')) { if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active'); $('.js-add-award.is-active').removeClass('is-active');
$('.emoji-menu').removeClass('is-visible'); this.hideMenuElement($('.emoji-menu'));
} }
} }
}); });
...@@ -88,12 +91,12 @@ class AwardsHandler { ...@@ -88,12 +91,12 @@ class AwardsHandler {
if ($menu.length) { if ($menu.length) {
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
$menu.removeClass('is-visible'); this.hideMenuElement($menu);
$('.js-emoji-menu-search').blur(); $('.js-emoji-menu-search').blur();
} else { } else {
$addBtn.addClass('is-active'); $addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn); this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible'); this.showMenuElement($menu);
$('.js-emoji-menu-search').focus(); $('.js-emoji-menu-search').focus();
} }
} else { } else {
...@@ -103,7 +106,7 @@ class AwardsHandler { ...@@ -103,7 +106,7 @@ class AwardsHandler {
$addBtn.removeClass('is-loading'); $addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn); this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => { return setTimeout(() => {
$createdMenu.addClass('is-visible'); this.showMenuElement($createdMenu);
$('.js-emoji-menu-search').focus(); $('.js-emoji-menu-search').focus();
}, 200); }, 200);
}); });
...@@ -241,7 +244,8 @@ class AwardsHandler { ...@@ -241,7 +244,8 @@ class AwardsHandler {
if (isInIssuePage() && !isMainAwardsBlock) { if (isInIssuePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', ''); const id = votesBlock.attr('id').replace('note_', '');
$('.emoji-menu').removeClass('is-visible'); this.hideMenuElement($('.emoji-menu'));
$('.js-add-award.is-active').removeClass('is-active'); $('.js-add-award.is-active').removeClass('is-active');
const toggleAwardEvent = new CustomEvent('toggleAward', { const toggleAwardEvent = new CustomEvent('toggleAward', {
detail: { detail: {
...@@ -261,7 +265,8 @@ class AwardsHandler { ...@@ -261,7 +265,8 @@ class AwardsHandler {
return typeof callback === 'function' ? callback() : undefined; return typeof callback === 'function' ? callback() : undefined;
}); });
$('.emoji-menu').removeClass('is-visible'); this.hideMenuElement($('.emoji-menu'));
return $('.js-add-award.is-active').removeClass('is-active'); return $('.js-add-award.is-active').removeClass('is-active');
} }
...@@ -529,6 +534,33 @@ class AwardsHandler { ...@@ -529,6 +534,33 @@ class AwardsHandler {
return $matchingElements.closest('li').clone(); return $matchingElements.closest('li').clone();
} }
/* showMenuElement and hideMenuElement are performance optimizations. We use
* opacity to show/hide the emoji menu, because we can animate it. But opacity
* leaves hidden elements in the render tree, which is unacceptable given the number
* of emoji elements in the emoji menu (5k+). To get the best of both worlds, we separately
* apply IS_RENDERED to add/remove the menu from the render tree and IS_VISIBLE to animate
* the menu being opened and closed. */
showMenuElement($emojiMenu) {
$emojiMenu.addClass(IS_RENDERED);
// enqueues animation as a microtask, so it begins ASAP once IS_RENDERED added
return Promise.resolve()
.then(() => $emojiMenu.addClass(IS_VISIBLE));
}
hideMenuElement($emojiMenu) {
$emojiMenu.on(transitionEndEventString, (e) => {
if (e.currentTarget === e.target) {
$emojiMenu
.removeClass(IS_RENDERED)
.off(transitionEndEventString);
}
});
$emojiMenu.removeClass(IS_VISIBLE);
}
destroy() { destroy() {
this.eventListeners.forEach((entry) => { this.eventListeners.forEach((entry) => {
entry.element.off.call(entry.element, ...entry.args); entry.element.off.call(entry.element, ...entry.args);
......
...@@ -69,8 +69,7 @@ ...@@ -69,8 +69,7 @@
@click="onClickAction(action.path)" @click="onClickAction(action.path)"
:class="{ disabled: isActionDisabled(action) }" :class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"> :disabled="isActionDisabled(action)">
<span v-html="playIconSvg"></span> {{action.name}}
<span>{{action.name}}</span>
</button> </button>
</li> </li>
</ul> </ul>
......
...@@ -39,11 +39,7 @@ ...@@ -39,11 +39,7 @@
rel="nofollow" rel="nofollow"
download download
:href="artifact.path"> :href="artifact.path">
<i Download {{artifact.name}} artifacts
class="fa fa-download"
aria-hidden="true">
</i>
<span>Download {{artifact.name}} artifacts</span>
</a> </a>
</li> </li>
</ul> </ul>
......
<script> <script>
import { s__ } from '../../locale';
const PAGINATION_UI_BUTTON_LIMIT = 4; const PAGINATION_UI_BUTTON_LIMIT = 4;
const UI_LIMIT = 6; const UI_LIMIT = 6;
const SPREAD = '...'; const SPREAD = '...';
const PREV = 'Prev'; const PREV = s__('Pagination|Prev');
const NEXT = 'Next'; const NEXT = s__('Pagination|Next');
const FIRST = '« First'; const FIRST = s__('Pagination|« First');
const LAST = 'Last »'; const LAST = s__('Pagination|Last »');
export default { export default {
props: { props: {
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
} }
.emoji-menu { .emoji-menu {
display: none;
position: absolute; position: absolute;
top: 0; top: 0;
margin-top: 3px; margin-top: 3px;
...@@ -27,6 +28,10 @@ ...@@ -27,6 +28,10 @@
transition: .3s cubic-bezier(.67, .06, .19, 1.44); transition: .3s cubic-bezier(.67, .06, .19, 1.44);
transition-property: transform, opacity; transition-property: transform, opacity;
&.is-rendered {
display: block;
}
&.is-aligned-right { &.is-aligned-right {
transform-origin: 100% -45px; transform-origin: 100% -45px;
} }
......
...@@ -35,10 +35,13 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -35,10 +35,13 @@ class Projects::TreeController < Projects::ApplicationController
end end
format.json do format.json do
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree) render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
end end
end end
end end
end
def create_dir def create_dir
return render_404 unless @commit_params.values.all? return render_404 unless @commit_params.values.all?
......
...@@ -131,7 +131,7 @@ module GroupsHelper ...@@ -131,7 +131,7 @@ module GroupsHelper
end end
def default_help def default_help
s_("GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner.") s_("GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually.")
end end
def ancestor_locked_but_you_can_override(group) def ancestor_locked_but_you_can_override(group)
......
...@@ -10,6 +10,7 @@ class Repository ...@@ -10,6 +10,7 @@ class Repository
RESERVED_REFS_NAMES = %W[ RESERVED_REFS_NAMES = %W[
heads heads
tags tags
replace
#{REF_ENVIRONMENTS} #{REF_ENVIRONMENTS}
#{REF_KEEP_AROUND} #{REF_KEEP_AROUND}
#{REF_ENVIRONMENTS} #{REF_ENVIRONMENTS}
......
...@@ -36,7 +36,9 @@ ...@@ -36,7 +36,9 @@
= s_('Branches|Delete merged branches') = s_('Branches|Delete merged branches')
= link_to new_project_branch_path(@project), class: 'btn btn-create' do = link_to new_project_branch_path(@project), class: 'btn btn-create' do
= s_('Branches|New branch') = s_('Branches|New branch')
= render 'projects/commits/mirror_status' = render 'projects/commits/mirror_status'
- if @branches.any? - if @branches.any?
%ul.content-list.all-branches %ul.content-list.all-branches
- @branches.each do |branch| - @branches.each do |branch|
......
...@@ -11,19 +11,15 @@ ...@@ -11,19 +11,15 @@
#{ _('Source code') } #{ _('Source code') }
%li %li
= link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download zip') %span= _('Download zip')
%li %li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.gz') %span= _('Download tar.gz')
%li %li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.bz2') %span= _('Download tar.bz2')
%li %li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar') %span= _('Download tar')
- if pipeline && pipeline.latest_builds_with_artifacts.any? - if pipeline && pipeline.latest_builds_with_artifacts.any?
...@@ -36,6 +32,5 @@ ...@@ -36,6 +32,5 @@
- pipeline.latest_builds_with_artifacts.each do |job| - pipeline.latest_builds_with_artifacts.each do |job|
%li %li
= link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span %span
#{s_('DownloadArtifacts|Download')} '#{job.name}' #{s_('DownloadArtifacts|Download')} '#{job.name}'
...@@ -11,19 +11,16 @@ ...@@ -11,19 +11,16 @@
- if can_create_issue - if can_create_issue
%li %li
= link_to new_project_issue_path(@project) do = link_to new_project_issue_path(@project) do
= icon('exclamation-circle fw')
#{ _('New issue') } #{ _('New issue') }
- if merge_project - if merge_project
%li %li
= link_to project_new_merge_request_path(merge_project) do = link_to project_new_merge_request_path(merge_project) do
= icon('tasks fw')
#{ _('New merge request') } #{ _('New merge request') }
- if can_create_snippet - if can_create_snippet
%li %li
= link_to new_project_snippet_path(@project) do = link_to new_project_snippet_path(@project) do
= icon('file-text-o fw')
#{ _('New snippet') } #{ _('New snippet') }
- if can_create_issue || merge_project || can_create_snippet - if can_create_issue || merge_project || can_create_snippet
...@@ -32,20 +29,16 @@ ...@@ -32,20 +29,16 @@
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') } #{ _('New file') }
%li %li
= link_to new_project_branch_path(@project) do = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') } #{ _('New branch') }
%li %li
= link_to new_project_tag_path(@project) do = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') } #{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project) - elsif current_user && current_user.already_forked?(@project)
%li %li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') } #{ _('New file') }
- elsif can?(current_user, :fork_project, @project) - elsif can?(current_user, :fork_project, @project)
%li %li
...@@ -55,5 +48,4 @@ ...@@ -55,5 +48,4 @@
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
= icon('file fw')
#{ _('New file') } #{ _('New file') }
...@@ -20,15 +20,12 @@ ...@@ -20,15 +20,12 @@
- if can_edit_tree? - if can_edit_tree?
%li %li
= link_to project_new_blob_path(@project, @id) do = link_to project_new_blob_path(@project, @id) do
= icon('pencil fw')
#{ _('New file') } #{ _('New file') }
%li %li
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
= icon('file fw')
#{ _('Upload file') } #{ _('Upload file') }
%li %li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
= icon('folder fw')
#{ _('New directory') } #{ _('New directory') }
- elsif can?(current_user, :fork_project, @project) - elsif can?(current_user, :fork_project, @project)
%li %li
...@@ -38,7 +35,6 @@ ...@@ -38,7 +35,6 @@
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
= icon('pencil fw')
#{ _('New file') } #{ _('New file') }
%li %li
- continue_params = { to: request.fullpath, - continue_params = { to: request.fullpath,
...@@ -47,7 +43,6 @@ ...@@ -47,7 +43,6 @@
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
= icon('file fw')
#{ _('Upload file') } #{ _('Upload file') }
%li %li
- continue_params = { to: request.fullpath, - continue_params = { to: request.fullpath,
...@@ -56,15 +51,12 @@ ...@@ -56,15 +51,12 @@
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
= icon('folder fw')
#{ _('New directory') } #{ _('New directory') }
%li.divider %li.divider
%li %li
= link_to new_project_branch_path(@project) do = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') } #{ _('New branch') }
%li %li
= link_to new_project_tag_path(@project) do = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') } #{ _('New tag') }
---
title: Allow the git circuit breaker to correctly handle missing repository storages
merge_request: 14417
author:
type: fixed
---
title: Fix `rake gitlab:incoming_email:check` and make it report the actual error
merge_request: 14423
author:
type: fixed
---
title: Also reserve refs/replace after importing a project
merge_request: 14436
author:
type: fixed
---
title: Index projects on repository storage
merge_request: 14414
author:
type: other
---
title: Replace the 'project/shortcuts.feature' spinach test with an rspec analog
merge_request: 14431
author: Vitaliy @blackst0ne Klachkov
type: other
...@@ -578,9 +578,7 @@ Settings.backup['upload']['storage_class'] ||= nil ...@@ -578,9 +578,7 @@ Settings.backup['upload']['storage_class'] ||= nil
# Git # Git
# #
Settings['git'] ||= Settingslogic.new({}) Settings['git'] ||= Settingslogic.new({})
Settings.git['max_size'] ||= 20971520 # 20.megabytes
Settings.git['bin_path'] ||= '/usr/bin/git' Settings.git['bin_path'] ||= '/usr/bin/git'
Settings.git['timeout'] ||= 10
# Important: keep the satellites.path setting until GitLab 9.0 at # Important: keep the satellites.path setting until GitLab 9.0 at
# least. This setting is fed to 'rm -rf' in # least. This setting is fed to 'rm -rf' in
......
...@@ -33,7 +33,7 @@ class MigrateUserExternalMailData < ActiveRecord::Migration ...@@ -33,7 +33,7 @@ class MigrateUserExternalMailData < ActiveRecord::Migration
SELECT true SELECT true
FROM user_synced_attributes_metadata FROM user_synced_attributes_metadata
WHERE user_id = users.id WHERE user_id = users.id
AND provider = users.email_provider AND provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL)
) )
AND id BETWEEN #{start_id} AND #{end_id} AND id BETWEEN #{start_id} AND #{end_id}
EOF EOF
......
class AddProjectRepositoryStorageIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(*index_spec) unless index_exists?(*index_spec)
end
def down
remove_concurrent_index(*index_spec) if index_exists?(*index_spec)
end
def index_spec
[:projects, :repository_storage]
end
end
...@@ -33,7 +33,7 @@ class PostDeployMigrateUserExternalMailData < ActiveRecord::Migration ...@@ -33,7 +33,7 @@ class PostDeployMigrateUserExternalMailData < ActiveRecord::Migration
SELECT true SELECT true
FROM user_synced_attributes_metadata FROM user_synced_attributes_metadata
WHERE user_id = users.id WHERE user_id = users.id
AND provider = users.email_provider AND provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL)
) )
AND id BETWEEN #{start_id} AND #{end_id} AND id BETWEEN #{start_id} AND #{end_id}
EOF EOF
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170918223303) do ActiveRecord::Schema.define(version: 20170921115009) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1528,6 +1528,7 @@ ActiveRecord::Schema.define(version: 20170918223303) do ...@@ -1528,6 +1528,7 @@ ActiveRecord::Schema.define(version: 20170918223303) do
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
add_index "projects", ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
......
...@@ -33,7 +33,7 @@ This is the typeface used for code blocks and references to commits, branches, a ...@@ -33,7 +33,7 @@ This is the typeface used for code blocks and references to commits, branches, a
## Icons ## Icons
GitLab has a strong, unique personality. When you look at any screen, you should know immediately know that it is GitLab. GitLab has a strong, unique personality. When you look at any screen, you should know immediately that it is GitLab.
Iconography is a powerful visual cue to the user and is a great way for us to reflect our particular sense of style. Iconography is a powerful visual cue to the user and is a great way for us to reflect our particular sense of style.
- **Standard size:** 16px * 16px - **Standard size:** 16px * 16px
......
...@@ -596,6 +596,30 @@ See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubyd ...@@ -596,6 +596,30 @@ See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubyd
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd> <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
</dl> </dl>
#### Details and Summary
Content can be collapsed using HTML's [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) tags. This is especially useful for collapsing long logs so they take up less screen space.
<p>
<details>
<summary>Click me to collapse/fold.</summary>
These details will remain hidden until expanded.
<pre><code>PASTE LOGS HERE</code></pre>
</details>
</p>
**Note:** Unfortunately Markdown is not supported inside these tags, as described by the [markdown specification](https://daringfireball.net/projects/markdown/syntax#html). You can work around this by using HTML, for example you can use `<pre><code>` tags instead of [code fences](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting).
```html
<details>
<summary>Click me to collapse/fold.</summary>
These details will remain hidden until expanded.
<pre><code>PASTE LOGS HERE</code></pre>
</details>
```
### Horizontal Rule ### Horizontal Rule
``` ```
......
@dashboard
Feature: Project Shortcuts
Background:
Given I sign in as a user
And I own a project
And I visit my project's commits page
@javascript
Scenario: Navigate to files tab
Given I press "g" and "f"
Then the active main tab should be Repository
Then the active sub tab should be Files
@javascript
Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c"
Then the active main tab should be Repository
Then the active sub tab should be Commits
@javascript
Scenario: Navigate to graph tab
Given I press "g" and "n"
Then the active sub tab should be Graph
And the active main tab should be Repository
@javascript
Scenario: Navigate to repository charts tab
Given I press "g" and "d"
Then the active sub tab should be Charts
And the active main tab should be Repository
@javascript
Scenario: Navigate to issues tab
Given I press "g" and "i"
Then the active main tab should be Issues
@javascript
Scenario: Navigate to merge requests tab
Given I press "g" and "m"
Then the active main tab should be Merge Requests
@javascript
Scenario: Navigate to snippets tab
Given I press "g" and "s"
Then the active main tab should be Snippets
@javascript
Scenario: Navigate to wiki tab
Given I press "g" and "w"
Then the active main tab should be Wiki
@javascript
Scenario: Navigate to project home
Given I press "g" and "p"
Then the active sub tab should be Home
And the active main tab should be Project
@javascript
Scenario: Navigate to project feed
Given I press "g" and "e"
Then the active sub tab should be Activity
And the active main tab should be Project
class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
include SharedProjectTab
include SharedShortcuts
step 'I press "g" and "f"' do
find('body').native.send_key('g')
find('body').native.send_key('f')
end
step 'I press "g" and "c"' do
find('body').native.send_key('g')
find('body').native.send_key('c')
end
step 'I press "g" and "n"' do
find('body').native.send_key('g')
find('body').native.send_key('n')
end
step 'I press "g" and "d"' do
find('body').native.send_key('g')
find('body').native.send_key('d')
end
step 'I press "g" and "s"' do
find('body').native.send_key('g')
find('body').native.send_key('s')
end
step 'I press "g" and "w"' do
find('body').native.send_key('g')
find('body').native.send_key('w')
end
step 'I press "g" and "e"' do
find('body').native.send_key('g')
find('body').native.send_key('e')
end
end
...@@ -64,9 +64,12 @@ module Gitlab ...@@ -64,9 +64,12 @@ module Gitlab
# For performance purposes maximum 20 latest commits # For performance purposes maximum 20 latest commits
# will be passed as post receive hook data. # will be passed as post receive hook data.
commit_attrs = commits_limited.map do |commit| # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38259
commit_attrs = Gitlab::GitalyClient.allow_n_plus_1_calls do
commits_limited.map do |commit|
commit.hook_attrs(with_changed_files: true) commit.hook_attrs(with_changed_files: true)
end end
end
type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push' type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
delegate :new_file?, :deleted_file?, :renamed_file?, delegate :new_file?, :deleted_file?, :renamed_file?,
:old_path, :new_path, :a_mode, :b_mode, :mode_changed?, :old_path, :new_path, :a_mode, :b_mode, :mode_changed?,
:submodule?, :expanded?, :too_large?, :collapsed?, :line_count, to: :diff, prefix: false :submodule?, :expanded?, :too_large?, :collapsed?, :line_count, :has_binary_notice?, to: :diff, prefix: false
# Finding a viewer for a diff file happens based only on extension and whether the # Finding a viewer for a diff file happens based only on extension and whether the
# diff file blobs are binary or text, which means 1 diff file should only be matched by 1 viewer, # diff file blobs are binary or text, which means 1 diff file should only be matched by 1 viewer,
...@@ -166,7 +166,7 @@ module Gitlab ...@@ -166,7 +166,7 @@ module Gitlab
end end
def binary? def binary?
old_blob&.binary? || new_blob&.binary? has_binary_notice? || old_blob&.binary? || new_blob&.binary?
end end
def text? def text?
......
...@@ -206,6 +206,10 @@ module Gitlab ...@@ -206,6 +206,10 @@ module Gitlab
Diff.binary_message(@old_path, @new_path) Diff.binary_message(@old_path, @new_path)
end end
def has_binary_notice?
@diff.start_with?('Binary')
end
private private
def init_from_rugged(rugged) def init_from_rugged(rugged)
......
...@@ -475,7 +475,15 @@ module Gitlab ...@@ -475,7 +475,15 @@ module Gitlab
# diff options. The +options+ hash can also include :break_rewrites to # diff options. The +options+ hash can also include :break_rewrites to
# split larger rewrites into delete/add pairs. # split larger rewrites into delete/add pairs.
def diff(from, to, options = {}, *paths) def diff(from, to, options = {}, *paths)
Gitlab::Git::DiffCollection.new(diff_patches(from, to, options, *paths), options) iterator = gitaly_migrate(:diff_between) do |is_enabled|
if is_enabled
gitaly_commit_client.diff(from, to, options.merge(paths: paths))
else
diff_patches(from, to, options, *paths)
end
end
Gitlab::Git::DiffCollection.new(iterator, options)
end end
# Returns a RefName for a given SHA # Returns a RefName for a given SHA
......
...@@ -11,6 +11,7 @@ module Gitlab ...@@ -11,6 +11,7 @@ module Gitlab
end end
CircuitOpen = Class.new(Inaccessible) CircuitOpen = Class.new(Inaccessible)
Misconfiguration = Class.new(Inaccessible)
REDIS_KEY_PREFIX = 'storage_accessible:'.freeze REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
......
...@@ -28,14 +28,26 @@ module Gitlab ...@@ -28,14 +28,26 @@ module Gitlab
def self.for_storage(storage) def self.for_storage(storage)
cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do cached_circuitbreakers = RequestStore.fetch(:circuitbreaker_cache) do
Hash.new do |hash, storage_name| Hash.new do |hash, storage_name|
hash[storage_name] = new(storage_name) hash[storage_name] = build(storage_name)
end end
end end
cached_circuitbreakers[storage] cached_circuitbreakers[storage]
end end
def initialize(storage, hostname = Gitlab::Environment.hostname) def self.build(storage, hostname = Gitlab::Environment.hostname)
config = Gitlab.config.repositories.storages[storage]
if !config.present?
NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Storage '#{storage}' is not configured"))
elsif !config['path'].present?
NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Path for storage '#{storage}' is not configured"))
else
new(storage, hostname)
end
end
def initialize(storage, hostname)
@storage = storage @storage = storage
@hostname = hostname @hostname = hostname
...@@ -64,6 +76,10 @@ module Gitlab ...@@ -64,6 +76,10 @@ module Gitlab
recent_failure || too_many_failures recent_failure || too_many_failures
end end
def failure_info
@failure_info ||= get_failure_info
end
# Memoizing the `storage_available` call means we only do it once per # Memoizing the `storage_available` call means we only do it once per
# request when the storage is available. # request when the storage is available.
# #
...@@ -121,10 +137,12 @@ module Gitlab ...@@ -121,10 +137,12 @@ module Gitlab
end end
end end
def failure_info def cache_key
@failure_info ||= get_failure_info @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end end
private
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)
...@@ -134,10 +152,6 @@ module Gitlab ...@@ -134,10 +152,6 @@ module Gitlab
FailureInfo.new(last_failure, failure_count.to_i) FailureInfo.new(last_failure, failure_count.to_i)
end end
def cache_key
@cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end
end end
end end
end end
......
...@@ -78,7 +78,7 @@ module Gitlab ...@@ -78,7 +78,7 @@ module Gitlab
def failing_circuit_breakers def failing_circuit_breakers
@failing_circuit_breakers ||= failing_on_hosts.map do |hostname| @failing_circuit_breakers ||= failing_on_hosts.map do |hostname|
CircuitBreaker.new(storage_name, hostname) CircuitBreaker.build(storage_name, hostname)
end end
end end
......
module Gitlab
module Git
module Storage
class NullCircuitBreaker
# These will have actual values
attr_reader :storage,
:hostname
# These will always have nil values
attr_reader :storage_path,
:failure_wait_time,
:failure_reset_time,
:storage_timeout
def initialize(storage, hostname, error: nil)
@storage = storage
@hostname = hostname
@error = error
end
def perform
@error ? raise(@error) : yield
end
def circuit_broken?
!!@error
end
def failure_count_threshold
1
end
def last_failure
circuit_broken? ? Time.now : nil
end
def failure_count
circuit_broken? ? 1 : 0
end
def failure_info
Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(last_failure, failure_count)
end
end
end
end
end
...@@ -32,20 +32,38 @@ module Gitlab ...@@ -32,20 +32,38 @@ module Gitlab
GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value
end end
def diff(from, to, options = {})
from_id = case from
when NilClass
EMPTY_TREE_ID
when Rugged::Commit
from.oid
else
from
end
to_id = case to
when NilClass
EMPTY_TREE_ID
when Rugged::Commit
to.oid
else
to
end
request_params = diff_between_commits_request_params(from_id, to_id, options)
call_commit_diff(request_params, options)
end
def diff_from_parent(commit, options = {}) def diff_from_parent(commit, options = {})
request_params = commit_diff_request_params(commit, options) request_params = diff_from_parent_request_params(commit, options)
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true)
request_params[:collapse_diffs] = request_params[:enforce_limits] || !options.fetch(:expanded, true)
request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
request = Gitaly::CommitDiffRequest.new(request_params) call_commit_diff(request_params, options)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
GitalyClient::DiffStitcher.new(response)
end end
def commit_deltas(commit) def commit_deltas(commit)
request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit)) request = Gitaly::CommitDeltaRequest.new(diff_from_parent_request_params(commit))
response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request) response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request)
response.flat_map { |msg| msg.deltas } response.flat_map { |msg| msg.deltas }
...@@ -214,13 +232,28 @@ module Gitlab ...@@ -214,13 +232,28 @@ module Gitlab
private private
def commit_diff_request_params(commit, options = {}) def call_commit_diff(request_params, options = {})
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true)
request_params[:collapse_diffs] = request_params[:enforce_limits] || !options.fetch(:expanded, true)
request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
GitalyClient::DiffStitcher.new(response)
end
def diff_from_parent_request_params(commit, options = {})
parent_id = commit.parent_ids.first || EMPTY_TREE_ID parent_id = commit.parent_ids.first || EMPTY_TREE_ID
diff_between_commits_request_params(parent_id, commit.id, options)
end
def diff_between_commits_request_params(from_id, to_id, options)
{ {
repository: @gitaly_repo, repository: @gitaly_repo,
left_commit_id: parent_id, left_commit_id: from_id,
right_commit_id: commit.id, right_commit_id: to_id,
paths: options.fetch(:paths, []).map { |path| GitalyClient.encode(path) } paths: options.fetch(:paths, []).map { |path| GitalyClient.encode(path) }
} }
end end
......
...@@ -125,7 +125,7 @@ module Gitlab ...@@ -125,7 +125,7 @@ module Gitlab
end end
def storage_circuitbreaker_test(storage_name) def storage_circuitbreaker_test(storage_name)
Gitlab::Git::Storage::CircuitBreaker.new(storage_name).perform { "OK" } Gitlab::Git::Storage::CircuitBreaker.build(storage_name).perform { "OK" }
rescue Gitlab::Git::Storage::Inaccessible rescue Gitlab::Git::Storage::Inaccessible
nil nil
end end
......
...@@ -4,22 +4,17 @@ module SystemCheck ...@@ -4,22 +4,17 @@ module SystemCheck
set_name 'IMAP server credentials are correct?' set_name 'IMAP server credentials are correct?'
def check? def check?
if mailbox_config if config
begin try_connect_imap
imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) else
imap.starttls if config[:start_tls] @error = "#{mail_room_config_path} does not have mailboxes setup"
imap.login(config[:email], config[:password]) false
connected = true
rescue
connected = false
end end
end end
connected
end
def show_error def show_error
try_fixing_it( try_fixing_it(
"An error occurred: #{@error.class}: #{@error.message}",
'Check that the information in config/gitlab.yml is correct' 'Check that the information in config/gitlab.yml is correct'
) )
for_more_information( for_more_information(
...@@ -30,15 +25,31 @@ module SystemCheck ...@@ -30,15 +25,31 @@ module SystemCheck
private private
def mailbox_config def try_connect_imap
return @config if @config imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
imap.starttls if config[:start_tls]
imap.login(config[:email], config[:password])
true
rescue => error
@error = error
false
end
def config
@config ||= load_config
end
def mail_room_config_path
@mail_room_config_path ||=
Rails.root.join('config', 'mail_room.yml').to_s
end
config_path = Rails.root.join('config', 'mail_room.yml').to_s def load_config
erb = ERB.new(File.read(config_path)) erb = ERB.new(File.read(mail_room_config_path))
erb.filename = config_path erb.filename = mail_room_config_path
config_file = YAML.load(erb.result) config_file = YAML.load(erb.result)
@config = config_file[:mailboxes]&.first config_file.dig(:mailboxes, 0)
end end
end end
end end
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
# This file is distributed under the same license as the gitlab package. # This file is distributed under the same license as the gitlab package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-06 08:32+0200\n" "POT-Creation-Date: 2017-09-21 14:20+0530\n"
"PO-Revision-Date: 2017-09-06 08:32+0200\n" "PO-Revision-Date: 2017-09-21 14:20+0530\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -52,6 +53,9 @@ msgid_plural "%d pipelines" ...@@ -52,6 +53,9 @@ msgid_plural "%d pipelines"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "1st contribution!"
msgstr ""
msgid "A collection of graphs regarding Continuous Integration" msgid "A collection of graphs regarding Continuous Integration"
msgstr "" msgstr ""
...@@ -94,7 +98,7 @@ msgstr "" ...@@ -94,7 +98,7 @@ msgstr ""
msgid "All" msgid "All"
msgstr "" msgstr ""
msgid "Appearances" msgid "Appearance"
msgstr "" msgstr ""
msgid "Applications" msgid "Applications"
...@@ -118,64 +122,37 @@ msgstr "" ...@@ -118,64 +122,37 @@ msgstr ""
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}" msgid "Artifacts"
msgstr ""
msgid "Authentication log"
msgstr ""
msgid "Billing"
msgstr ""
msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan."
msgstr "" msgstr ""
msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available." msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
msgid "BillingPlans|Current plan"
msgstr ""
msgid "BillingPlans|Customer Support"
msgstr ""
msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}."
msgstr ""
msgid "BillingPlans|Manage plan"
msgstr ""
msgid "BillingPlans|Please contact %{customer_support_link} in that case."
msgstr ""
msgid "BillingPlans|See all %{plan_name} features"
msgstr "" msgstr ""
msgid "BillingPlans|This group uses the plan associated with its parent group." msgid "Authentication Log"
msgstr "" msgstr ""
msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." msgid "Auto DevOps (Beta)"
msgstr "" msgstr ""
msgid "BillingPlans|Upgrade" msgid "Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
msgstr "" msgstr ""
msgid "BillingPlans|You are currently on the %{plan_link} plan." msgid "Auto DevOps documentation"
msgstr "" msgstr ""
msgid "BillingPlans|frequently asked questions" msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly."
msgstr "" msgstr ""
msgid "BillingPlans|monthly" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
msgstr "" msgstr ""
msgid "BillingPlans|paid annually at %{price_per_year}" msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly."
msgstr "" msgstr ""
msgid "BillingPlans|per user" msgid "AutoDevOps|Learn more in the"
msgstr "" msgstr ""
msgid "Billinglans|Downgrade" msgid "Board"
msgstr "" msgstr ""
msgid "Branch" msgid "Branch"
...@@ -195,6 +172,12 @@ msgstr "" ...@@ -195,6 +172,12 @@ msgstr ""
msgid "Branches" msgid "Branches"
msgstr "" msgstr ""
msgid "Branches|This branch hasn’t been merged into %{default_branch}."
msgstr ""
msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
msgstr ""
msgid "Browse Directory" msgid "Browse Directory"
msgstr "" msgstr ""
...@@ -338,18 +321,12 @@ msgstr "" ...@@ -338,18 +321,12 @@ msgstr ""
msgid "Compare" msgid "Compare"
msgstr "" msgstr ""
msgid "Container Registry"
msgstr ""
msgid "Contribution guide" msgid "Contribution guide"
msgstr "" msgstr ""
msgid "Contributors" msgid "Contributors"
msgstr "" msgstr ""
msgid "Copy SSH public key to clipboard"
msgstr ""
msgid "Copy URL to clipboard" msgid "Copy URL to clipboard"
msgstr "" msgstr ""
...@@ -490,6 +467,9 @@ msgstr "" ...@@ -490,6 +467,9 @@ msgstr ""
msgid "Emails" msgid "Emails"
msgstr "" msgstr ""
msgid "Enable in settings"
msgstr ""
msgid "EventFilterBy|Filter by all" msgid "EventFilterBy|Filter by all"
msgstr "" msgstr ""
...@@ -517,6 +497,9 @@ msgstr "" ...@@ -517,6 +497,9 @@ msgstr ""
msgid "Every week (Sundays at 4:00am)" msgid "Every week (Sundays at 4:00am)"
msgstr "" msgstr ""
msgid "Explore projects"
msgstr ""
msgid "Failed to change the owner" msgid "Failed to change the owner"
msgstr "" msgstr ""
...@@ -558,9 +541,6 @@ msgstr "" ...@@ -558,9 +541,6 @@ msgstr ""
msgid "GPG Keys" msgid "GPG Keys"
msgstr "" msgstr ""
msgid "Geo Nodes"
msgstr ""
msgid "Git storage health information has been reset" msgid "Git storage health information has been reset"
msgstr "" msgstr ""
...@@ -573,7 +553,28 @@ msgstr "" ...@@ -573,7 +553,28 @@ msgstr ""
msgid "GoToYourFork|Fork" msgid "GoToYourFork|Fork"
msgstr "" msgstr ""
msgid "Group overview" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
msgid "GroupSettings|Share with group lock"
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
msgstr ""
msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner."
msgstr ""
msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
msgstr ""
msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
msgstr "" msgstr ""
msgid "Health Check" msgid "Health Check"
...@@ -597,9 +598,6 @@ msgstr "" ...@@ -597,9 +598,6 @@ msgstr ""
msgid "Home" msgid "Home"
msgstr "" msgstr ""
msgid "Hooks"
msgstr ""
msgid "Housekeeping successfully started" msgid "Housekeeping successfully started"
msgstr "" msgstr ""
...@@ -621,6 +619,9 @@ msgstr "" ...@@ -621,6 +619,9 @@ msgstr ""
msgid "Issues" msgid "Issues"
msgstr "" msgstr ""
msgid "Jobs"
msgstr ""
msgid "LFSStatus|Disabled" msgid "LFSStatus|Disabled"
msgstr "" msgstr ""
...@@ -662,17 +663,11 @@ msgstr "" ...@@ -662,17 +663,11 @@ msgstr ""
msgid "Leave project" msgid "Leave project"
msgstr "" msgstr ""
msgid "License"
msgstr ""
msgid "Limited to showing %d event at most" msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most" msgid_plural "Limited to showing %d events at most"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Locked Files"
msgstr ""
msgid "Median" msgid "Median"
msgstr "" msgstr ""
...@@ -813,6 +808,18 @@ msgstr "" ...@@ -813,6 +808,18 @@ msgstr ""
msgid "Owner" msgid "Owner"
msgstr "" msgstr ""
msgid "Pagination|Last »"
msgstr ""
msgid "Pagination|Next"
msgstr ""
msgid "Pagination|Prev"
msgstr ""
msgid "Pagination|« First"
msgstr ""
msgid "Password" msgid "Password"
msgstr "" msgstr ""
...@@ -828,9 +835,6 @@ msgstr "" ...@@ -828,9 +835,6 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "Pipeline quota"
msgstr ""
msgid "PipelineCharts|Failed:" msgid "PipelineCharts|Failed:"
msgstr "" msgstr ""
...@@ -918,10 +922,7 @@ msgstr "" ...@@ -918,10 +922,7 @@ msgstr ""
msgid "Preferences" msgid "Preferences"
msgstr "" msgstr ""
msgid "Profile Settings" msgid "Profile"
msgstr ""
msgid "Project"
msgstr "" msgstr ""
msgid "Project '%{project_name}' queued for deletion." msgid "Project '%{project_name}' queued for deletion."
...@@ -957,9 +958,6 @@ msgstr "" ...@@ -957,9 +958,6 @@ msgstr ""
msgid "Project home" msgid "Project home"
msgstr "" msgstr ""
msgid "Project overview"
msgstr ""
msgid "ProjectActivityRSS|Subscribe" msgid "ProjectActivityRSS|Subscribe"
msgstr "" msgstr ""
...@@ -984,22 +982,22 @@ msgstr "" ...@@ -984,22 +982,22 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph" msgid "ProjectNetworkGraph|Graph"
msgstr "" msgstr ""
msgid "Push Rules" msgid "ProjectsDropdown|Frequently visited"
msgstr "" msgstr ""
msgid "ProjectsDropdown|Loading projects" msgid "ProjectsDropdown|Loading projects"
msgstr "" msgstr ""
msgid "ProjectsDropdown|Sorry, no projects matched your search"
msgstr ""
msgid "ProjectsDropdown|Projects you visit often will appear here" msgid "ProjectsDropdown|Projects you visit often will appear here"
msgstr "" msgstr ""
msgid "ProjectsDropdown|Search your projects" msgid "ProjectsDropdown|Search your projects"
msgstr "" msgstr ""
msgid "ProjectsDropdown|Something went wrong on our end" msgid "ProjectsDropdown|Something went wrong on our end."
msgstr ""
msgid "ProjectsDropdown|Sorry, no projects matched your search"
msgstr "" msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support" msgid "ProjectsDropdown|This feature requires browser localStorage support"
...@@ -1074,6 +1072,9 @@ msgstr "" ...@@ -1074,6 +1072,9 @@ msgstr ""
msgid "Schedule a new pipeline" msgid "Schedule a new pipeline"
msgstr "" msgstr ""
msgid "Schedules"
msgstr ""
msgid "Scheduling Pipelines" msgid "Scheduling Pipelines"
msgstr "" msgstr ""
...@@ -1113,6 +1114,12 @@ msgstr "" ...@@ -1113,6 +1114,12 @@ msgstr ""
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
msgid "Show parent pages"
msgstr ""
msgid "Show parent subgroups"
msgstr ""
msgid "Showing %d event" msgid "Showing %d event"
msgid_plural "Showing %d events" msgid_plural "Showing %d events"
msgstr[0] "" msgstr[0] ""
...@@ -1133,6 +1140,9 @@ msgstr "" ...@@ -1133,6 +1140,9 @@ msgstr ""
msgid "StarProject|Star" msgid "StarProject|Star"
msgstr "" msgstr ""
msgid "Starred projects"
msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr "" msgstr ""
...@@ -1142,6 +1152,9 @@ msgstr "" ...@@ -1142,6 +1152,9 @@ msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
msgstr "" msgstr ""
msgid "System Hooks"
msgstr ""
msgid "Tag" msgid "Tag"
msgid_plural "Tags" msgid_plural "Tags"
msgstr[0] "" msgstr[0] ""
...@@ -1207,6 +1220,9 @@ msgstr "" ...@@ -1207,6 +1220,9 @@ msgstr ""
msgid "There are problems accessing Git storage: " msgid "There are problems accessing Git storage: "
msgstr "" msgstr ""
msgid "This is the author's first Merge Request to this project. Handle with care."
msgstr ""
msgid "This means you can not push code until you create an empty repository or import existing one." msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "" msgstr ""
...@@ -1457,6 +1473,9 @@ msgstr "" ...@@ -1457,6 +1473,9 @@ msgstr ""
msgid "Your name" msgid "Your name"
msgstr "" msgstr ""
msgid "Your projects"
msgstr ""
msgid "day" msgid "day"
msgid_plural "days" msgid_plural "days"
msgstr[0] "" msgstr[0] ""
......
require 'spec_helper'
feature 'Project shortcuts' do
let(:project) { create(:project, name: 'Victorialand') }
let(:user) { create(:user) }
describe 'On a project', js: true do
before do
project.team << [user, :master]
sign_in user
visit project_path(project)
end
describe 'pressing "i"' do
it 'redirects to new issue page' do
find('body').native.send_key('i')
expect(page).to have_content('Victorialand')
end
end
end
end
require 'spec_helper'
describe 'User uses shortcuts', :js do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_path(project))
end
context 'when navigating to the Overview pages' do
it 'redirects to the details page' do
find('body').native.send_key('g')
find('body').native.send_key('p')
expect(page).to have_active_navigation('Overview')
expect(page).to have_active_sub_navigation('Details')
end
it 'redirects to the activity page' do
find('body').native.send_key('g')
find('body').native.send_key('e')
expect(page).to have_active_navigation('Overview')
expect(page).to have_active_sub_navigation('Activity')
end
end
context 'when navigating to the Repository pages' do
it 'redirects to the repository files page' do
find('body').native.send_key('g')
find('body').native.send_key('f')
expect(page).to have_active_navigation('Repository')
expect(page).to have_active_sub_navigation('Files')
end
it 'redirects to the repository commits page' do
find('body').native.send_key('g')
find('body').native.send_key('c')
expect(page).to have_active_navigation('Repository')
expect(page).to have_active_sub_navigation('Commits')
end
it 'redirects to the repository graph page' do
find('body').native.send_key('g')
find('body').native.send_key('n')
expect(page).to have_active_navigation('Repository')
expect(page).to have_active_sub_navigation('Graph')
end
it 'redirects to the repository charts page' do
find('body').native.send_key('g')
find('body').native.send_key('d')
expect(page).to have_active_navigation('Repository')
expect(page).to have_active_sub_navigation('Charts')
end
end
context 'when navigating to the Issues pages' do
it 'redirects to the issues list page' do
find('body').native.send_key('g')
find('body').native.send_key('i')
expect(page).to have_active_navigation('Issues')
expect(page).to have_active_sub_navigation('List')
end
it 'redirects to the new issue page' do
find('body').native.send_key('i')
expect(page).to have_content(project.title)
end
end
context 'when navigating to the Merge Requests pages' do
it 'redirects to the merge requests page' do
find('body').native.send_key('g')
find('body').native.send_key('m')
expect(page).to have_active_navigation('Merge Requests')
end
end
context 'when navigating to the Snippets pages' do
it 'redirects to the snippets page' do
find('body').native.send_key('g')
find('body').native.send_key('s')
expect(page).to have_active_navigation('Snippets')
end
end
context 'when navigating to the Wiki pages' do
it 'redirects to the wiki page' do
find('body').native.send_key('g')
find('body').native.send_key('w')
expect(page).to have_active_navigation('Wiki')
end
end
end
...@@ -34,7 +34,7 @@ describe('Pipelines Artifacts dropdown', () => { ...@@ -34,7 +34,7 @@ describe('Pipelines Artifacts dropdown', () => {
).toEqual(artifacts[0].path); ).toEqual(artifacts[0].path);
expect( expect(
component.$el.querySelector('.dropdown-menu li a span').textContent, component.$el.querySelector('.dropdown-menu li a').textContent,
).toContain(artifacts[0].name); ).toContain(artifacts[0].name);
}); });
}); });
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do
let(:storage_name) { 'default' } let(:storage_name) { 'default' }
let(:circuit_breaker) { described_class.new(storage_name) } let(:circuit_breaker) { described_class.new(storage_name, hostname) }
let(:hostname) { Gitlab::Environment.hostname } let(:hostname) { Gitlab::Environment.hostname }
let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" } let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
...@@ -22,7 +22,8 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: ...@@ -22,7 +22,8 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
'failure_wait_time' => 30, 'failure_wait_time' => 30,
'failure_reset_time' => 1800, 'failure_reset_time' => 1800,
'storage_timeout' => 5 'storage_timeout' => 5
} },
'nopath' => { 'path' => nil }
) )
end end
...@@ -59,6 +60,14 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: ...@@ -59,6 +60,14 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
expect(breaker).to be_a(described_class) expect(breaker).to be_a(described_class)
expect(described_class.for_storage('default')).to eq(breaker) expect(described_class.for_storage('default')).to eq(breaker)
end end
it 'returns a broken circuit breaker for an unknown storage' do
expect(described_class.for_storage('unknown').circuit_broken?).to be_truthy
end
it 'returns a broken circuit breaker when the path is not set' do
expect(described_class.for_storage('nopath').circuit_broken?).to be_truthy
end
end end
describe '#initialize' do describe '#initialize' do
......
require 'spec_helper'
describe Gitlab::Git::Storage::NullCircuitBreaker do
let(:storage) { 'default' }
let(:hostname) { 'localhost' }
let(:error) { nil }
subject(:breaker) { described_class.new(storage, hostname, error: error) }
context 'with an error' do
let(:error) { Gitlab::Git::Storage::Misconfiguration.new('error') }
describe '#perform' do
it { expect { breaker.perform { 'ok' } }.to raise_error(error) }
end
describe '#circuit_broken?' do
it { expect(breaker.circuit_broken?).to be_truthy }
end
describe '#last_failure' do
it { Timecop.freeze { expect(breaker.last_failure).to eq(Time.now) } }
end
describe '#failure_count' do
it { expect(breaker.failure_count).to eq(breaker.failure_count_threshold) }
end
describe '#failure_info' do
it { Timecop.freeze { expect(breaker.failure_info).to eq(Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(Time.now, breaker.failure_count_threshold)) } }
end
end
context 'not broken' do
describe '#perform' do
it { expect(breaker.perform { 'ok' }).to eq('ok') }
end
describe '#circuit_broken?' do
it { expect(breaker.circuit_broken?).to be_falsy }
end
describe '#last_failure' do
it { expect(breaker.last_failure).to be_nil }
end
describe '#failure_count' do
it { expect(breaker.failure_count).to eq(0) }
end
describe '#failure_info' do
it { expect(breaker.failure_info).to eq(Gitlab::Git::Storage::CircuitBreaker::FailureInfo.new(nil, 0)) }
end
end
describe '#failure_count_threshold' do
it { expect(breaker.failure_count_threshold).to eq(1) }
end
it 'implements the CircuitBreaker interface' do
ours = described_class.public_instance_methods
theirs = Gitlab::Git::Storage::CircuitBreaker.public_instance_methods
# These methods are not part of the public API, but are public to allow the
# CircuitBreaker specs to operate. They should be made private over time.
exceptions = %i[
cache_key
check_storage_accessible!
no_failures?
storage_available?
track_storage_accessible
track_storage_inaccessible
]
expect(theirs - ours).to contain_exactly(*exceptions)
end
end
...@@ -21,7 +21,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -21,7 +21,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
let(:metric_class) { Gitlab::HealthChecks::Metric } let(:metric_class) { Gitlab::HealthChecks::Metric }
let(:result_class) { Gitlab::HealthChecks::Result } let(:result_class) { Gitlab::HealthChecks::Result }
let(:repository_storages) { [:default] } let(:repository_storages) { ['default'] }
let(:tmp_dir) { Dir.mktmpdir } let(:tmp_dir) { Dir.mktmpdir }
let(:storages_paths) do let(:storages_paths) do
...@@ -64,7 +64,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -64,7 +64,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
allow(described_class).to receive(:storage_circuitbreaker_test) { true } allow(described_class).to receive(:storage_circuitbreaker_test) { true }
end end
it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) } it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: 'default')) }
end end
context 'storage points to directory that has both read and write rights' do context 'storage points to directory that has both read and write rights' do
...@@ -72,7 +72,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -72,7 +72,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
FileUtils.chmod_R(0755, tmp_dir) FileUtils.chmod_R(0755, tmp_dir)
end end
it { is_expected.to include(result_class.new(true, nil, shard: :default)) } it { is_expected.to include(result_class.new(true, nil, shard: 'default')) }
it 'cleans up files used for testing' do it 'cleans up files used for testing' do
expect(described_class).to receive(:storage_write_test).with(any_args).and_call_original expect(described_class).to receive(:storage_write_test).with(any_args).and_call_original
...@@ -85,7 +85,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -85,7 +85,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
allow(described_class).to receive(:storage_read_test).with(any_args).and_return(false) allow(described_class).to receive(:storage_read_test).with(any_args).and_return(false)
end end
it { is_expected.to include(result_class.new(false, 'cannot read from storage', shard: :default)) } it { is_expected.to include(result_class.new(false, 'cannot read from storage', shard: 'default')) }
end end
context 'write test fails' do context 'write test fails' do
...@@ -93,7 +93,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -93,7 +93,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
allow(described_class).to receive(:storage_write_test).with(any_args).and_return(false) allow(described_class).to receive(:storage_write_test).with(any_args).and_return(false)
end end
it { is_expected.to include(result_class.new(false, 'cannot write to storage', shard: :default)) } it { is_expected.to include(result_class.new(false, 'cannot write to storage', shard: 'default')) }
end end
end end
end end
...@@ -109,7 +109,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -109,7 +109,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
it 'provides metrics' do it 'provides metrics' do
metrics = described_class.metrics metrics = described_class.metrics
expect(metrics).to all(have_attributes(labels: { shard: :default })) expect(metrics).to all(have_attributes(labels: { shard: 'default' }))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
...@@ -128,7 +128,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -128,7 +128,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
it 'provides metrics' do it 'provides metrics' do
metrics = described_class.metrics metrics = described_class.metrics
expect(metrics).to all(have_attributes(labels: { shard: :default })) expect(metrics).to all(have_attributes(labels: { shard: 'default' }))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
...@@ -156,14 +156,14 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -156,14 +156,14 @@ describe Gitlab::HealthChecks::FsShardsCheck do
describe '#readiness' do describe '#readiness' do
subject { described_class.readiness } subject { described_class.readiness }
it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) } it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: 'default')) }
end end
describe '#metrics' do describe '#metrics' do
it 'provides metrics' do it 'provides metrics' do
metrics = described_class.metrics metrics = described_class.metrics
expect(metrics).to all(have_attributes(labels: { shard: :default })) expect(metrics).to all(have_attributes(labels: { shard: 'default' }))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
......
...@@ -4,3 +4,9 @@ RSpec::Matchers.define :have_active_navigation do |expected| ...@@ -4,3 +4,9 @@ RSpec::Matchers.define :have_active_navigation do |expected|
expect(page.find('.sidebar-top-level-items > li.active')).to have_content(expected) expect(page.find('.sidebar-top-level-items > li.active')).to have_content(expected)
end end
end end
RSpec::Matchers.define :have_active_sub_navigation do |expected|
match do |page|
expect(page.find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)')).to have_content(expected)
end
end
...@@ -52,7 +52,7 @@ module StubConfiguration ...@@ -52,7 +52,7 @@ module StubConfiguration
# Default storage is always required # Default storage is always required
messages['default'] ||= Gitlab.config.repositories.storages.default messages['default'] ||= Gitlab.config.repositories.storages.default
messages.each do |storage_name, storage_settings| messages.each do |storage_name, storage_settings|
storage_settings['path'] ||= TestEnv.repos_path storage_settings['path'] = TestEnv.repos_path unless storage_settings.key?('path')
storage_settings['failure_count_threshold'] ||= 10 storage_settings['failure_count_threshold'] ||= 10
storage_settings['failure_wait_time'] ||= 30 storage_settings['failure_wait_time'] ||= 30
storage_settings['failure_reset_time'] ||= 1800 storage_settings['failure_reset_time'] ||= 1800
......
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