Commit eaca44e2 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 4310-security-reports

* master: (76 commits)
  Add changelog entry
  Include mock replication slot WAL
  Hide sync status when event timestamp is not present
  Show empty state when timestamp is not present
  Update common.rb
  Merge branch 'qa-secret-variables-scenario' into tmp
  Just don't expand if it's already expanded
  Use `numberToHumanSize` for memory unit conversion
  Add multiple steps to limitations.
  Resolve scripts/lint-rugged conflicts
  fix bug in webpack_helper in which force_same_domain argument was ignored breaking local rspec tests
  Merge branch 'sh-fix-geo-migration-path' into 'master'
  Fix rubocop offenses introduced in !16623
  Fix spec/ee/spec/lib/gitlab/geo/health_check_spec.rb
  fix documentation about node version
  Ensure the  job also run for tags
  Fix broken paths
  Fix conflicts for app/assets/javascripts/vue_merge_request_widget/dependencies.js
  Fix conflicts for app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
  Fix conflicts for app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
  ...
parents 405b6954 c3efd8e0
......@@ -413,7 +413,6 @@ spinach-mysql 2 3: *spinach-metadata-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
<<: *pull-cache
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
......@@ -434,6 +433,12 @@ static-analysis:
stage: test
script:
- scripts/static-analysis
cache:
key: "ruby-2.3.6-with-yarn-and-rubocop"
paths:
- vendor/ruby
- .yarn-cache/
- tmp/rubocop_cache
# Documentation checks:
# - Check validity of relative links
......@@ -742,8 +747,6 @@ pages:
cache gems:
<<: *dedicated-runner
<<: *pull-cache
only:
- tags
variables:
SETUP_DB: "false"
script:
......@@ -754,6 +757,7 @@ cache gems:
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags
gitlab_git_test:
<<: *dedicated-runner
......
......@@ -18,6 +18,7 @@ AllCops:
- 'bin/**/*'
- 'generator_templates/**/*'
- 'builds/**/*'
CacheRootDirectory: tmp
# This cop checks whether some constant value isn't a
# mutable literal (e.g. array or hash).
......
......@@ -422,7 +422,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.76.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -309,7 +309,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.76.0)
gitaly-proto (0.78.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -1091,7 +1091,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.76.0)
gitaly-proto (~> 0.78.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
......@@ -180,7 +180,7 @@ const Api = {
issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key)
.replace(':key', encodeURIComponent(key))
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
......
......@@ -53,10 +53,10 @@
</i>
</div>
<div class="deploy-key-content key-list-item-info">
<strong class="title">
<strong class="title qa-key-title">
{{ deployKey.title }}
</strong>
<div class="description">
<div class="description qa-key-fingerprint">
{{ deployKey.fingerprint }}
</div>
</div>
......
......@@ -18,7 +18,7 @@ function renderWithKaTeX(elements) {
const display = $this.attr('data-math-style') === 'display';
try {
katex.render($this.text(), mathNode.get(0), { displayMode: display });
katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
mathNode.insertAfter($this);
$this.remove();
} catch (err) {
......
......@@ -2,7 +2,7 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon';
import StatusIcon from './mr_widget_status_icon.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
......
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
props: {
status: { type: String, required: true },
showDisabledButton: { type: Boolean, required: false },
},
components: {
ciIcon,
loadingIcon,
},
computed: {
statusObj() {
return {
group: this.status,
icon: `status_${this.status}`,
};
},
},
template: `
<div class="space-children flex-container-block append-right-10">
<div v-if="status === 'loading'" class="mr-widget-icon">
<loading-icon />
</div>
<ci-icon v-else :status="statusObj" />
<button
v-if="showDisabledButton"
type="button"
class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true">
Merge
</button>
</div>
`,
};
<script>
import ciIcon from '../../vue_shared/components/ci_icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
ciIcon,
loadingIcon,
},
props: {
status: {
type: String,
required: true,
},
showDisabledButton: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isLoading() {
return this.status === 'loading';
},
statusObj() {
return {
group: this.status,
icon: `status_${this.status}`,
};
},
},
};
</script>
<template>
<div class="space-children flex-container-block append-right-10">
<div
v-if="isLoading"
class="mr-widget-icon"
>
<loading-icon />
</div>
<ci-icon
v-else
:status="statusObj"
/>
<button
v-if="showDisabledButton"
type="button"
class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true"
>
{{ s__("mrWidget|Merge") }}
</button>
</div>
</template>
<script>
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetArchived',
......
<script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetAutoMergeFailed',
......
<script>
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetChecking',
......
<script>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetClosed',
......
<script>
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetConflicts',
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
......
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import MRWidgetAuthor from '../../components/mr_widget_author';
import eventHub from '../../event_hub';
......
......@@ -2,7 +2,7 @@ import Flash from '../../../flash';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import tooltip from '../../../vue_shared/directives/tooltip';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
......
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetMerging',
props: {
mr: { type: Object, required: true },
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body mr-state-locked media">
<status-icon status="loading" />
<div class="media-body">
<h4>
This merge request is in the process of being merged
</h4>
<section class="mr-info-list">
<p>
The changes will be merged into
<span class="label-branch">
<a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
</span>
</p>
</section>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetMerging',
components: {
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body mr-state-locked media">
<status-icon status="loading" />
<div class="media-body">
<h4>
{{ s__("mrWidget|This merge request is in the process of being merged") }}
</h4>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes will be merged into") }}
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
</p>
</section>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help';
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetNotAllowed',
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetPipelineBlocked',
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetPipelineBlocked',
......
......@@ -3,7 +3,7 @@ import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
export default {
......
<script>
import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Flash from '../../../flash';
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetSHAMismatch',
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetUnresolvedDiscussions',
......
import statusIcon from '../mr_widget_status_icon';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
......
......@@ -19,7 +19,7 @@ export { default as WidgetRelatedLinks } from './components/mr_widget_related_li
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging';
export { default as MergingState } from './components/states/mr_widget_merging.vue';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
......
......@@ -314,8 +314,8 @@
}
&.invalid {
@include status-color($gray-dark, $gray, $common-gray-dark);
border-color: $common-gray-light;
@include status-color($gray-dark, $gray, $gray-darkest);
border-color: $gray-darkest;
}
}
......@@ -339,8 +339,8 @@
&.invalid {
svg {
border: 1px solid $common-gray-light;
fill: $common-gray-light;
border: 1px solid $gray-darkest;
fill: $gray-darkest;
}
}
......
......@@ -8,7 +8,8 @@ class HealthController < ActionController::Base
Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck,
Gitlab::HealthChecks::Redis::SharedStateCheck,
Gitlab::HealthChecks::FsShardsCheck
Gitlab::HealthChecks::FsShardsCheck,
Gitlab::HealthChecks::GitalyCheck
].freeze
def readiness
......
......@@ -14,13 +14,13 @@ module AutoDevopsHelper
if missing_service
params = {
kubernetes: link_to('Kubernetes service', edit_project_service_path(project, 'kubernetes'))
kubernetes: link_to('Kubernetes cluster', project_clusters_path(project))
}
if missing_domain
_('Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly.') % params
_('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params
else
_('Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly.') % params
_('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params
end
elsif missing_domain
_('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
......
......@@ -2,7 +2,7 @@ require 'webpack/rails/manifest'
module WebpackHelper
def webpack_bundle_tag(bundle, force_same_domain: false)
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: true))
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain))
end
# override webpack-rails gem helper until changes can make it upstream
......
......@@ -1016,8 +1016,14 @@ class MergeRequest < ActiveRecord::Base
merged_at = metrics&.merged_at
notes_association = notes_with_associations
# It is not guaranteed that Note#created_at will be strictly later than
# MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
# comparison, as will a HA environment if clocks are not *precisely*
# synchronized. Add a minute's leeway to compensate for both possibilities
cutoff = merged_at - 1.minute
if merged_at
notes_association = notes_association.where('created_at > ?', merged_at)
notes_association = notes_association.where('created_at >= ?', cutoff)
end
!merge_commit.has_been_reverted?(current_user, notes_association)
......
......@@ -25,7 +25,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
delegate :bundle_to_disk, :create_from_bundle, to: :raw_repository
CreateTreeError = Class.new(StandardError)
......
......@@ -13,11 +13,11 @@
- if @user.avatar?
You can change your avatar here
- if gravatar_enabled?
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host}
- else
You can upload an avatar here
- if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, 'http://' + Gitlab.config.gravatar.host}
or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host}
.col-lg-8
.clearfix.avatar-image.append-bottom-default
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
......
......@@ -9,15 +9,15 @@
- else
.row-content-block.second-block.center
%h3.page-title
%h4
This project does not have a README yet
- if can?(current_user, :push_code, @project)
%p
A
%code README
file contains information about other files in a repository and is commonly
distributed with computer software, forming part of its documentation.
GitLab will render it here instead of this message.
%p
We recommend you to
= 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.
= link_to "Add Readme", add_special_file_path(@project, file_name: 'README.md'), class: 'btn btn-new'
......@@ -6,7 +6,10 @@
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
- can_create_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- can_create_snippet = can?(current_user, :create_snippet, @project)
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if can_create_issue || merge_project || can_create_project_snippet
%li.dropdown-header= _('This project')
- if can_create_issue
%li= link_to _('New issue'), new_project_issue_path(@project)
......@@ -14,11 +17,11 @@
- if merge_project
%li= link_to _('New merge request'), project_new_merge_request_path(merge_project)
- if can_create_snippet
- if can_create_project_snippet
%li= link_to _('New snippet'), new_project_snippet_path(@project)
- if can_create_issue || merge_project || can_create_snippet
%li.divider
- if can?(current_user, :push_code, @project)
%li.dropdown-header= _('This repository')
- if can?(current_user, :push_code, @project)
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
......@@ -31,5 +34,5 @@
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
%li= link_to _('New file'), fork_path, method: :post
- title = capture do
This commit was signed with a different user's verified signature.
- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless', show_user: true }
- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
......@@ -2,6 +2,6 @@
This commit was signed with a verified signature, but the committer email
is <strong>not verified</strong> to belong to the same user.
- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'icon_status_notfound_borderless', show_user: true }
- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
......@@ -10,7 +10,7 @@
- title = capture do
.gpg-popover-status
.gpg-popover-icon{ class: css_class }
= render "shared/icons/#{icon}.svg"
= sprite_icon(icon)
%div
= title
......
- title = capture do
This commit was signed with an <strong>unverified</strong> signature.
- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless' }
- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless' }
= render partial: 'projects/commit/signature_badge', locals: locals
......@@ -2,6 +2,6 @@
This commit was signed with a <strong>verified</strong> signature and the
committer email is verified to belong to the same user.
- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'icon_status_success_borderless', show_user: true }
- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'status_success_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
......@@ -6,8 +6,9 @@
= render "home_panel"
.row-content-block.second-block.center
%h3.page-title
%h4
The repository for this project is empty
- if can?(current_user, :push_code, @project)
%p
If you already have files you can push them using command line instructions below.
......@@ -28,8 +29,8 @@
%p
- link = link_to(s_('AutoDevOps|Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'))
= s_('AutoDevOps|You can activate %{link_to_settings} for this project.').html_safe % { link_to_settings: link }
%p
= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master'), class: 'btn btn-new'
- if can?(current_user, :push_code, @project)
%div{ class: container_class }
......@@ -79,4 +80,4 @@
- if can? current_user, :remove_project, @project
.prepend-top-20
= link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
= link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove pull-right"
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0V11.78a5.9 5.9 0 0 0 .827-.492z" fill-rule="nonzero"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></svg>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.4583333,12.375 L8.70008808,12.375 C8.45889044,12.375 8.25,12.5826293 8.25,12.8387529 L8.25,14.2029137 C8.25,14.4551799 8.4515113,14.6666667 8.70008808,14.6666667 L12.9619841,14.6666667 C13.3891296,14.6666667 13.75,14.3193051 13.75,13.8908129 L13.75,13.2899463 L13.75,6.42552703 C13.75,6.16226705 13.5423707,5.95833333 13.2862471,5.95833333 L11.9220863,5.95833333 C11.6698201,5.95833333 11.4583333,6.16750307 11.4583333,6.42552703 L11.4583333,12.375 Z" id="Combined-Shape" transform="translate(11.000000, 10.312500) rotate(-315.000000) translate(-11.000000, -10.312500) "></path></svg>
......@@ -58,15 +58,15 @@
= icon('skype')
- unless @user.linkedin.blank?
.profile-link-holder.middle-dot-divider
= link_to linkedin_url(@user), title: "LinkedIn" do
= link_to linkedin_url(@user), title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('linkedin-square')
- unless @user.twitter.blank?
.profile-link-holder.middle-dot-divider
= link_to twitter_url(@user), title: "Twitter" do
= link_to twitter_url(@user), title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('twitter-square')
- unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link'
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'noopener noreferrer nofollow'
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider
= icon('map-marker')
......
---
title: Handle empty event timestamp and larger memory units
merge_request: 4206
author:
type: fixed
---
title: Handle special characters on API request of issuable templates
merge_request: 15323
author: Takuya Noguchi
type: fixed
---
title: Link Auto DevOps settings to Clusters page
merge_request: 16641
author:
type: changed
---
title: Fix encoding issue when counting commit count
merge_request: 16637
author:
type: fixed
---
title: Replace verified badge icons and uniform colors
merge_request:
author:
type: fixed
---
title: Default to HTTPS for all Gravatar URLs
merge_request: 16666
author:
type: fixed
---
title: Disable throwOnError in KaTeX to reveal user where is the problem
merge_request: 16684
author: Jakub Jirutka
type: other
---
title: Improve empty project overview
merge_request: 16617
author: George Tsiolis
type: added
---
title: Make Gitaly RepositoryExists opt-out
merge_request: 16680
author:
type: other
---
title: Add a gRPC health check to ensure Gitaly is up
merge_request:
author:
type: added
---
title: fix documentation about node version
merge_request: 16720
author: Tobias Gurtzick
type: other
---
title: Add note within ux documentation that further changes should be made within
the design.gitlab project
merge_request:
author:
type: deprecated
......@@ -197,10 +197,12 @@ production: &base
host: 'https://mattermost.example.com'
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
## If using gravatar.com, there's nothing to change here. For Libravatar
## you'll need to provide the custom URLs. For more information,
## see: https://docs.gitlab.com/ee/customization/libravatar.html
gravatar:
# gravatar urls: possible placeholders: %{hash} %{size} %{email} %{username}
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
# Gravatar/Libravatar URLs: possible placeholders: %{hash} %{size} %{email} %{username}
# plain_url: "http://..." # default: https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
## Auxiliary jobs
......
......@@ -410,7 +410,7 @@ Settings.mattermost['host'] = nil unless Settings.mattermost.enabled
#
Settings['gravatar'] ||= Settingslogic.new({})
Settings.gravatar['enabled'] = true if Settings.gravatar['enabled'].nil?
Settings.gravatar['plain_url'] ||= 'http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
Settings.gravatar['plain_url'] ||= 'https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
Settings.gravatar['host'] = Settings.host_without_www(Settings.gravatar['plain_url'])
......
......@@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do
#
# Templates
#
get '/templates/:template_type/:key' => 'templates#show', as: :template
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: /[^\/]+/ }
resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
......
......@@ -40,7 +40,6 @@
- [upload_checksum, 1]
- [repository_fork, 1]
- [repository_import, 1]
- [repository_remove_remote, 1]
- [github_importer, 1]
- [github_import_advance_stage, 1]
- [project_service, 1]
......@@ -72,6 +71,7 @@
# EE-specific queues
- [ldap_group_sync, 2]
- [geo, 1]
- [repository_remove_remote, 1]
- [repository_update_mirror, 1]
- [repository_update_remote_mirror, 1]
- [project_update_repository_storage, 1]
......
> We are in the process of transferring UX documentation to the [design.gitlab.com](https://gitlab.com/gitlab-org/design.gitlab.com) project. Any updates to these docs should be made in that project. If documentation does not yet exist within [design.gitlab.com](https://gitlab.com/gitlab-org/design.gitlab.com), [create an issue](https://gitlab.com/gitlab-org/design.gitlab.com/issues) and merge request to add your new changes.
# GitLab UX Guide
The goal of this guide is to provide standards, principles and in-depth information to design beautiful and effective GitLab features. This will be a living document, and we welcome contributions, feedback and suggestions.
......
......@@ -197,12 +197,11 @@ Read how to [replicate the Container Registry](docker_registry.md).
## Current limitations
- You cannot push code to secondary nodes
- The primary node has to be online for OAuth login to happen (existing
sessions and Git are not affected)
- It works for repos, wikis, issues, and merge requests
- It does not work for job logs, artifacts, GitLab Pages, and Docker images of the Container
- You cannot push code to secondary nodes, see [3912](https://gitlab.com/gitlab-org/gitlab-ee/issues/3912) for details.
- The primary node has to be online for OAuth login to happen (existing sessions and Git are not affected)
- It works for repos, wikis, issues, and merge requests, but it does not work for job logs, artifacts, GitLab Pages, and Docker images of the Container
Registry (by default, but you can configure it separately, see [replicate the Container Registry](docker_registry.md) for details)
- The installation takes multiple manual steps that together can take about an hour depending on circumstances; we are working on improving this experience, see [#2978](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2978) for details.
## Frequently Asked Questions
......
......@@ -66,9 +66,8 @@ To make full use of Auto DevOps, you will need:
a domain configured with wildcard DNS which is gonna be used by all of your
Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
1. **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
To enable deployments, you will need Kubernetes 1.5+. The [Kubernetes service][kubernetes-service]
integration will need to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters]
for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation.
1. **A load balancer** - You can use NGINX ingress by deploying it to your
Kubernetes cluster using the
......@@ -588,7 +587,7 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
```
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37115
[kubernetes-service]: ../../user/project/integrations/kubernetes.md
[kubernetes-clusters]: ../../user/project/clusters/index.md
[docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../../ci/review_apps/index.md
[container-registry]: ../../user/project/container_registry.md
......
......@@ -54,17 +54,16 @@ sudo gem install bundler --no-ri --no-rdoc
### 4. Update Node
GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and
it has a minimum requirement of node v4.3.0.
GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets.
We require a minimum version of node v6.0.0.
You can check which version you are running with `node -v`. If you are running
a version older than `v4.3.0` you will need to update to a newer version. You
a version older than `v6.0.0` you will need to update to a newer version. You
can find instructions to install from community maintained packages or compile
from source at the nodejs.org website.
<https://nodejs.org/en/download/>
Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
......
......@@ -56,17 +56,16 @@ sudo gem install bundler --no-ri --no-rdoc
### 4. Update Node
GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and
it has a minimum requirement of node v4.3.0.
GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets.
We require a minimum version of node v6.0.0.
You can check which version you are running with `node -v`. If you are running
a version older than `v4.3.0` you will need to update to a newer version. You
a version older than `v6.0.0` you will need to update to a newer version. You
can find instructions to install from community maintained packages or compile
from source at the nodejs.org website.
<https://nodejs.org/en/download/>
Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
JavaScript dependencies.
......
......@@ -2,7 +2,7 @@
/* eslint-disable vue/no-side-effects-in-computed-properties */
import { s__, __ } from '~/locale';
import { parseSeconds, stringifyTime } from '~/lib/utils/pretty_time';
import { bytesToMiB } from '~/lib/utils/number_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import icon from '~/vue_shared/components/icon.vue';
import { VALUE_TYPE, CUSTOM_TYPE } from '../constants';
......@@ -100,7 +100,7 @@
return `${this.nodeDetails.version} (${this.nodeDetails.revision})`;
},
replicationSlotWAL() {
return `${bytesToMiB(this.nodeDetails.replicationSlotWAL)} MB`;
return numberToHumanSize(this.nodeDetails.replicationSlotWAL);
},
dbReplicationLag() {
// Replication lag can be nil if the secondary isn't actually streaming
......
......@@ -35,16 +35,24 @@
<div
class="node-detail-value"
>
<strong>
{{ eventId }}
</strong>
<span
v-tooltip
class="event-status-timestamp"
data-placement="bottom"
:title="timeStampString"
<template v-if="eventTimeStamp">
<strong>
{{ eventId }}
</strong>
<span
v-tooltip
v-if="eventTimeStamp"
class="event-status-timestamp"
data-placement="bottom"
:title="timeStampString"
>
({{ timeFormated(timeStamp) }})
</span>
</template>
<strong
v-else
>
({{ timeFormated(timeStamp) }})
</span>
{{ __('Not available') }}
</strong>
</div>
</template>
......@@ -32,6 +32,9 @@
syncType() {
return this.namespaces.length > 0 ? s__('GeoNodes|Selective') : s__('GeoNodes|Full');
},
eventTimestampEmpty() {
return this.lastEvent.timeStamp === 0 || this.cursorLastEvent.timeStamp === 0;
},
syncLagInSeconds() {
return this.lagInSeconds(this.lastEvent.timeStamp, this.cursorLastEvent.timeStamp);
},
......@@ -79,7 +82,8 @@
return `${timeAgoStr} (${pendingEvents} events)`;
},
statusTooltip(lagInSeconds) {
if (lagInSeconds <= TIME_DIFF.FIVE_MINS) {
if (this.eventTimestampEmpty ||
lagInSeconds <= TIME_DIFF.FIVE_MINS) {
return '';
} else if (lagInSeconds > TIME_DIFF.FIVE_MINS &&
lagInSeconds <= TIME_DIFF.HOUR) {
......@@ -107,6 +111,7 @@
css-classes="sync-status-icon prepend-left-5"
/>
<span
v-if="!eventTimestampEmpty"
class="sync-status-event-info prepend-left-5"
>
{{ syncStatusEventInfo }}
......
......@@ -74,11 +74,11 @@ export default class GeoNodesStore {
failureCount: rawNodeDetails.attachments_failed_count || 0,
},
lastEvent: {
id: rawNodeDetails.last_event_id,
id: rawNodeDetails.last_event_id || 0,
timeStamp: rawNodeDetails.last_event_timestamp,
},
cursorLastEvent: {
id: rawNodeDetails.cursor_last_event_id,
id: rawNodeDetails.cursor_last_event_id || 0,
timeStamp: rawNodeDetails.cursor_last_event_timestamp,
},
namespaces: rawNodeDetails.namespaces,
......
import Flash from '~/flash';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import ApprovalsBody from './approvals_body';
import ApprovalsFooter from './approvals_footer';
......
<script>
import { __ } from '~/locale';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon';
/* eslint-disable vue/require-default-prop */
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import issuesBlock from './mr_widget_report_issues.vue';
......
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
export default {
props: {
......
......@@ -24,7 +24,7 @@ module Gitlab
def self.db_migrate_path
# Lazy initialisation so Rails.root will be defined
@db_migrate_path ||= File.join(Rails.root, 'db', 'geo', 'migrate')
@db_migrate_path ||= File.join(Rails.root, 'ee', 'db', 'geo', 'migrate')
end
def self.get_database_version
......
class Spinach::Features::User < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedUser
include SharedProject
step 'I should see user "John Doe" page' do
expect(title).to match(/^\s*John Doe/)
end
step 'I visit unsubscribe link' do
email = Base64.urlsafe_encode64("joh@doe.org")
visit "/unsubscribes/#{email}"
end
step 'I should see unsubscribe text and button' do
expect(page).to have_content "Unsubscribe from Admin notifications Yes, I want to unsubscribe joh@doe.org from any further admin emails."
end
step 'I press the unsubscribe button' do
click_button("Unsubscribe")
end
step 'I should be unsubscribed' do
expect(current_path).to eq root_path
end
step '"John Doe" has contributions' do
user = User.find_by(name: 'John Doe')
project = contributed_project
# Issue contribution
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(project, user, issue_params).execute
# Push code contribution
event = create(:push_event, project: project, author: user)
create(:push_event_payload, event: event, commit_count: 3)
end
step 'I should see contributed projects' do
page.within '#contributed' do
expect(page).to have_content(@contributed_project.name)
end
end
step 'I should see contributions calendar' do
expect(page).to have_css('.js-contrib-calendar')
end
def contributed_project
@contributed_project ||= create(:project, :public, :empty_repo)
end
end
Feature: User
Background:
Given User "John Doe" exists
And "John Doe" owns private project "Enterprise"
# Signed out
@javascript
Scenario: I visit user "John Doe" page while not signed in when he owns a public project
Given "John Doe" owns internal project "Internal"
And "John Doe" owns public project "Community"
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should not see project "Internal"
And I should see project "Community"
# Signed in as someone else
@javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project
Given "John Doe" owns public project "Community"
And "John Doe" owns internal project "Internal"
And I sign in as a user
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should see project "Internal"
And I should see project "Community"
@javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a public project
Given "John Doe" owns internal project "Internal"
And I sign in as a user
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should see project "Internal"
And I should not see project "Community"
@javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a project I can see
Given I sign in as a user
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should not see project "Internal"
And I should not see project "Community"
# Signed in as the user himself
@javascript
Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has a public project
Given "John Doe" owns internal project "Internal"
And "John Doe" owns public project "Community"
And I sign in as "John Doe"
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should see project "Enterprise"
And I should see project "Internal"
And I should see project "Community"
@javascript
Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has no public project
Given I sign in as "John Doe"
When I visit user "John Doe" page
And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should see project "Enterprise"
And I should not see project "Internal"
And I should not see project "Community"
Scenario: I unsubscribe from admin notifications
Given I sign in as "John Doe"
When I visit unsubscribe link
Then I should see unsubscribe text and button
And I press the unsubscribe button
Then I should be unsubscribed
@javascript
Scenario: "John Doe" contribution profile
Given I sign in as a user
And "John Doe" has contributions
When I visit user "John Doe" page
And I click on "Contributed projects" tab
Then I should see user "John Doe" page
And I should see contributed projects
And I should see contributions calendar
module Gitlab
module Git
# Parses root .gitattributes file at a given ref
class AttributesAtRefParser
delegate :attributes, to: :@parser
def initialize(repository, ref)
blob = repository.blob_at(ref, '.gitattributes')
@parser = AttributesParser.new(blob&.data)
end
end
end
end
# Gitaly note: JV: not sure what to make of this class. Why does it use
# the full disk path of the repository to look up attributes This is
# problematic in Gitaly, because Gitaly hides the full disk path to the
# repository from gitlab-ce.
module Gitlab
module Git
# Class for parsing Git attribute files and extracting the attributes for
# file patterns.
#
# Unlike Rugged this parser only needs a single IO call (a call to `open`),
# vastly reducing the time spent in extracting attributes.
#
# This class _only_ supports parsing the attributes file located at
# `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
# (`.gitattributes` is copied to this particular path).
#
# Basic usage:
#
# attributes = Gitlab::Git::Attributes.new(some_repo.path)
#
# attributes.attributes('README.md') # => { "eol" => "lf }
class Attributes
# path - The path to the Git repository.
def initialize(path)
@path = File.expand_path(path)
@patterns = nil
class AttributesParser
def initialize(attributes_data)
@data = attributes_data || ""
if @data.is_a?(File)
@patterns = parse_file
end
end
# Returns all the Git attributes for the given path.
#
# path - A path to a file for which to get the attributes.
# file_path - A path to a file for which to get the attributes.
#
# Returns a Hash.
def attributes(path)
full_path = File.join(@path, path)
def attributes(file_path)
absolute_path = File.join('/', file_path)
patterns.each do |pattern, attrs|
return attrs if File.fnmatch?(pattern, full_path)
return attrs if File.fnmatch?(pattern, absolute_path)
end
{}
......@@ -98,16 +82,10 @@ module Gitlab
# Iterates over every line in the attributes file.
def each_line
full_path = File.join(@path, 'info/attributes')
@data.each_line do |line|
break unless line.valid_encoding?
return unless File.exist?(full_path)
File.open(full_path, 'r') do |handle|
handle.each_line do |line|
break unless line.valid_encoding?
yield line.strip
end
yield line.strip
end
end
......@@ -125,7 +103,8 @@ module Gitlab
parsed = attrs ? parse_attributes(attrs) : {}
pairs << [File.join(@path, pattern), parsed]
absolute_pattern = File.join('/', pattern)
pairs << [absolute_pattern, parsed]
end
# Newer entries take precedence over older entries.
......
......@@ -70,11 +70,9 @@ module Gitlab
# Returns array of Gitlab::Git::Blob
# Does not guarantee blob data will be set
def batch_lfs_pointers(repository, blob_ids)
return [] if blob_ids.empty?
repository.gitaly_migrate(:batch_lfs_pointers) do |is_enabled|
if is_enabled
repository.gitaly_blob_client.batch_lfs_pointers(blob_ids)
repository.gitaly_blob_client.batch_lfs_pointers(blob_ids.to_a)
else
blob_ids.lazy
.select { |sha| possible_lfs_blob?(repository, sha) }
......
# Gitaly note: JV: not sure what to make of this class. Why does it use
# the full disk path of the repository to look up attributes This is
# problematic in Gitaly, because Gitaly hides the full disk path to the
# repository from gitlab-ce.
module Gitlab
module Git
# Parses gitattributes at `$GIT_DIR/info/attributes`
#
# Unlike Rugged this parser only needs a single IO call (a call to `open`),
# vastly reducing the time spent in extracting attributes.
#
# This class _only_ supports parsing the attributes file located at
# `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
# (`.gitattributes` is copied to this particular path).
#
# Basic usage:
#
# attributes = Gitlab::Git::InfoAttributes.new(some_repo.path)
#
# attributes.attributes('README.md') # => { "eol" => "lf }
class InfoAttributes
delegate :attributes, :patterns, to: :parser
# path - The path to the Git repository.
def initialize(path)
@repo_path = File.expand_path(path)
end
def parser
@parser ||= begin
if File.exist?(attributes_path)
File.open(attributes_path, 'r') do |file_handle|
AttributesParser.new(file_handle)
end
else
AttributesParser.new("")
end
end
end
private
def attributes_path
@attributes_path ||= File.join(@repo_path, 'info/attributes')
end
end
end
end
......@@ -102,7 +102,7 @@ module Gitlab
)
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
@attributes = Gitlab::Git::Attributes.new(path)
@attributes = Gitlab::Git::InfoAttributes.new(path)
end
def ==(other)
......@@ -133,7 +133,7 @@ module Gitlab
end
def exists?
Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
gitaly_repository_client.exists?
else
......@@ -490,7 +490,11 @@ module Gitlab
return []
end
log_by_shell(sha, options)
if log_using_shell?(options)
log_by_shell(sha, options)
else
log_by_walk(sha, options)
end
end
def count_commits(options)
......@@ -993,6 +997,18 @@ module Gitlab
attributes(path)[name]
end
# Check .gitattributes for a given ref
#
# This only checks the root .gitattributes file,
# it does not traverse subfolders to find additional .gitattributes files
#
# This method is around 30 times slower than `attributes`,
# which uses `$GIT_DIR/info/attributes`
def attributes_at(ref, file_path)
parser = AttributesAtRefParser.new(self, ref)
parser.attributes(file_path)
end
def languages(ref = nil)
Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled|
if is_enabled
......@@ -1172,6 +1188,19 @@ module Gitlab
end
end
def create_from_bundle(bundle_path)
gitaly_migrate(:create_repo_from_bundle) do |is_enabled|
if is_enabled
gitaly_repository_client.create_from_bundle(bundle_path)
else
run_git!(%W(clone --bare -- #{bundle_path} #{path}), chdir: nil)
self.class.create_hooks(path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
end
end
true
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
gitaly_migrate(:rebase) do |is_enabled|
if is_enabled
......@@ -1564,6 +1593,27 @@ module Gitlab
end
end
def log_using_shell?(options)
options[:path].present? ||
options[:disable_walk] ||
options[:skip_merges] ||
options[:after] ||
options[:before]
end
def log_by_walk(sha, options)
walk_options = {
show: sha,
sort: Rugged::SORT_NONE,
limit: options[:limit],
offset: options[:offset]
}
Rugged::Walker.walk(rugged, walk_options).to_a
end
# Gitaly note: JV: although #log_by_shell shells out to Git I think the
# complexity is such that we should migrate it as Ruby before trying to
# do it in Go.
def log_by_shell(sha, options)
limit = options[:limit].to_i
offset = options[:offset].to_i
......
require 'base64'
require 'gitaly'
require 'grpc/health/v1/health_pb'
require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
......@@ -69,14 +71,27 @@ module Gitlab
@stubs ||= {}
@stubs[storage] ||= {}
@stubs[storage][name] ||= begin
klass = Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
addr = address(storage)
addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
klass = stub_class(name)
addr = stub_address(storage)
klass.new(addr, :this_channel_is_insecure)
end
end
end
def self.stub_class(name)
if name == :health_check
Grpc::Health::V1::Health::Stub
else
Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
end
end
def self.stub_address(storage)
addr = address(storage)
addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
addr
end
def self.clear_stubs!
MUTEX.synchronize do
@stubs = nil
......
......@@ -34,6 +34,8 @@ module Gitlab
end
def batch_lfs_pointers(blob_ids)
return [] if blob_ids.empty?
request = Gitaly::GetLFSPointersRequest.new(
repository: @gitaly_repo,
blob_ids: blob_ids
......
......@@ -133,11 +133,11 @@ module Gitlab
def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
revision: ref
revision: encode_binary(ref)
)
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
request.path = options[:path] if options[:path].present?
request.path = encode_binary(options[:path]) if options[:path].present?
request.max_count = options[:max_count] if options[:max_count].present?
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
......
module Gitlab
module GitalyClient
class HealthCheckService
def initialize(storage)
@storage = storage
end
# Sends a gRPC health ping to the Gitaly server for the storage shard.
def check
request = Grpc::Health::V1::HealthCheckRequest.new
response = GitalyClient.call(@storage, :health_check, :check, request, timeout: GitalyClient.fast_timeout)
{ success: response&.status == :SERVING }
rescue GRPC::BadStatus => e
{ success: false, message: e.to_s }
end
end
end
end
......@@ -3,6 +3,8 @@ module Gitlab
class RepositoryService
include Gitlab::EncodingHelper
MAX_MSG_SIZE = 128.kilobytes.freeze
def initialize(repository)
@repository = repository
@gitaly_repo = repository.gitaly_repository
......@@ -178,6 +180,29 @@ module Gitlab
end
end
end
def create_from_bundle(bundle_path)
request = Gitaly::CreateRepositoryFromBundleRequest.new(repository: @gitaly_repo)
enum = Enumerator.new do |y|
File.open(bundle_path, 'rb') do |f|
while data = f.read(MAX_MSG_SIZE)
request.data = data
y.yield request
request = Gitaly::CreateRepositoryFromBundleRequest.new
end
end
end
GitalyClient.call(
@storage,
:repository_service,
:create_repository_from_bundle,
enum,
timeout: GitalyClient.default_timeout
)
end
end
end
end
module Gitlab
module HealthChecks
class GitalyCheck
extend BaseAbstractCheck
METRIC_PREFIX = 'gitaly_health_check'.freeze
class << self
def readiness
repository_storages.map do |storage_name|
check(storage_name)
end
end
def metrics
repository_storages.flat_map do |storage_name|
result, elapsed = with_timing { check(storage_name) }
labels = { shard: storage_name }
[
metric("#{metric_prefix}_success", successful?(result) ? 1 : 0, **labels),
metric("#{metric_prefix}_latency_seconds", elapsed, **labels)
].flatten
end
end
def check(storage_name)
serv = Gitlab::GitalyClient::HealthCheckService.new(storage_name)
result = serv.check
HealthChecks::Result.new(result[:success], result[:message], shard: storage_name)
end
private
def metric_prefix
METRIC_PREFIX
end
def successful?(result)
result[:success]
end
def repository_storages
storages.keys
end
def storages
Gitlab.config.repositories.storages
end
end
end
end
end
......@@ -11,11 +11,6 @@ module Gitlab
untar_with_options(archive: archive, dir: dir, options: 'zxf')
end
def git_clone_bundle(repo_path:, bundle_path:)
execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path}))
Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
end
def mkdir_p(path)
FileUtils.mkdir_p(path, mode: DEFAULT_MODE)
FileUtils.chmod(DEFAULT_MODE, path)
......
......@@ -13,7 +13,7 @@ module Gitlab
def restore
return true unless File.exist?(@path_to_bundle)
git_clone_bundle(repo_path: @project.repository.path_to_repo, bundle_path: @path_to_bundle)
@project.repository.create_from_bundle(@path_to_bundle)
rescue => e
@shared.error(e)
false
......
......@@ -195,13 +195,13 @@ msgstr ""
msgid "Author"
msgstr ""
msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly."
msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
msgstr ""
msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
msgstr ""
msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly."
msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
msgstr ""
msgid "AutoDevOps|Auto DevOps (Beta)"
......
......@@ -6,4 +6,5 @@ gem 'capybara-screenshot', '~> 1.0.18'
gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.8.0'
gem 'net-ssh', require: false
gem 'airborne', '~> 0.2.13'
......@@ -46,6 +46,7 @@ GEM
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.1)
net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
......@@ -97,6 +98,7 @@ DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
net-ssh
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
......
......@@ -11,6 +11,7 @@ module QA
autoload :Scenario, 'qa/runtime/scenario'
autoload :Browser, 'qa/runtime/browser'
autoload :Env, 'qa/runtime/env'
autoload :RSAKey, 'qa/runtime/rsa_key'
autoload :Address, 'qa/runtime/address'
autoload :API, 'qa/runtime/api'
end
......@@ -28,6 +29,7 @@ module QA
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
end
......@@ -118,6 +120,7 @@ module QA
autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
autoload :SecretVariables, 'qa/page/project/settings/secret_variables'
autoload :Runners, 'qa/page/project/settings/runners'
end
end
......
......@@ -10,6 +10,12 @@ module QA
end
end
product :fingerprint do
Page::Project::Settings::Repository.act do
expand_deploy_keys(&:key_fingerprint)
end
end
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-to-deploy'
project.description = 'project for adding deploy key test'
......
module QA
module Factory
module Resource
class SecretVariable < Factory::Base
attr_accessor :key, :value
product :key do
Page::Project::Settings::CICD.act do
expand_secret_variables(&:variable_key)
end
end
product :value do
Page::Project::Settings::CICD.act do
expand_secret_variables(&:variable_value)
end
end
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-secret-variables'
project.description = 'project for adding secret variable test'
end
def fabricate!
project.visit!
Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |setting|
setting.expand_secret_variables do |page|
page.fill_variable_key(key)
page.fill_variable_value(value)
page.add_variable
end
end
end
end
end
end
end
......@@ -41,7 +41,21 @@ module QA
end
def click_element(name)
find(Page::Element.new(name).selector_css).click
find_element(name).click
end
def find_element(name)
find(element_selector_css(name))
end
def within_element(name)
page.within(element_selector_css(name)) do
yield
end
end
def element_selector_css(name)
Page::Element.new(name).selector_css
end
def self.path
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment