Commit 59b5f127 authored by Nick Thomas's avatar Nick Thomas

Merge branch 'master' into 5612-invert-the-direction-of-geo-metrics-acquisition

parents b06f1781 572d3940
......@@ -529,7 +529,11 @@ compile-assets:
- date
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- date
- free -m
- bundle exec rake gitlab:assets:compile
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
artifacts:
expire_in: 7d
paths:
......@@ -826,10 +830,13 @@ gitlab:assets:compile:
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
NO_COMPRESSION: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
- date
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- date
- free -m
- bundle exec rake gitlab:assets:compile
artifacts:
name: webpack-report
......
......@@ -345,7 +345,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.5.0'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-rails', '~> 3.7.0'
gem 'rspec-retry', '~> 0.4.5'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
......
......@@ -771,36 +771,36 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
rspec (3.6.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
rspec-support (~> 3.7.0)
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-support (~> 3.7.0)
rspec-parameterized (0.4.0)
binding_of_caller
parser
proc_to_ast
rspec (>= 2.13, < 4)
unparser
rspec-rails (3.6.0)
rspec-rails (3.7.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
rspec-support (3.6.0)
rspec-support (3.7.1)
rspec_profiling (0.0.5)
activerecord
pg
......@@ -1178,7 +1178,7 @@ DEPENDENCIES
rouge (~> 3.1)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.6.0)
rspec-rails (~> 3.7.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
......
......@@ -95,7 +95,7 @@ export default class ClusterStore {
this.state.applications.jupyter.hostname =
serverAppEntry.hostname ||
(this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.xip.io`
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: '');
}
});
......
......@@ -31,10 +31,15 @@ export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => {
commit(rootTypes.CLEAR_PROJECTS, null, { root: true });
commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true });
commit(rootTypes.RESET_OPEN_FILES, null, { root: true });
dispatch('pipelines/stopPipelinePolling', null, { root: true });
dispatch('pipelines/clearEtagPoll', null, { root: true });
dispatch('pipelines/resetLatestPipeline', null, { root: true });
dispatch('setCurrentBranchId', '', { root: true });
dispatch('pipelines/stopPipelinePolling', null, { root: true })
.then(() => {
dispatch('pipelines/clearEtagPoll', null, { root: true });
})
.catch(e => {
throw e;
});
router.push(`/project/${projectPath}/merge_requests/${id}`);
};
......
......@@ -12,8 +12,12 @@ let eTagPoll;
export const clearEtagPoll = () => {
eTagPoll = null;
};
export const stopPipelinePolling = () => eTagPoll && eTagPoll.stop();
export const restartPipelinePolling = () => eTagPoll && eTagPoll.restart();
export const stopPipelinePolling = () => {
if (eTagPoll) eTagPoll.stop();
};
export const restartPipelinePolling = () => {
if (eTagPoll) eTagPoll.restart();
};
export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE);
export const receiveLatestPipelineError = ({ commit, dispatch }) => {
......@@ -51,9 +55,9 @@ export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {
Visibility.change(() => {
if (!Visibility.hidden()) {
eTagPoll.restart();
dispatch('restartPipelinePolling');
} else {
eTagPoll.stop();
dispatch('stopPipelinePolling');
}
});
};
......
......@@ -115,6 +115,10 @@ export default () => {
const dependencyScanningHelpPath = datasetOptions.dependencyScanningHelpPath;
const vulnerabilityFeedbackPath = datasetOptions.vulnerabilityFeedbackPath;
const vulnerabilityFeedbackHelpPath = datasetOptions.vulnerabilityFeedbackHelpPath;
const dastEndpoint = datasetOptions.dastEndpoint;
const sastContainerEndpoint = datasetOptions.sastContainerEndpoint;
const dastHelpPath = datasetOptions.dastHelpPath;
const sastContainerHelpPath = datasetOptions.sastContainerHelpPath;
const pipelineId = parseInt(datasetOptions.pipelineId, 10);
const store = createStore();
......@@ -164,6 +168,10 @@ export default () => {
vulnerabilityFeedbackPath,
vulnerabilityFeedbackHelpPath,
pipelineId,
dastHeadPath: dastEndpoint,
sastContainerHeadPath: sastContainerEndpoint,
dastHelpPath,
sastContainerHelpPath,
},
on: {
updateBadgeCount: this.updateBadge,
......
<script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
name: 'TimeTrackingComparisonPane',
directives: {
tooltip,
},
props: {
timeSpent: {
type: Number,
......@@ -51,17 +55,12 @@ export default {
<div class="time-tracking-comparison-pane">
<div
class="compare-meter"
data-toggle="tooltip"
data-placement="top"
role="timeRemainingDisplay"
:aria-valuenow="timeRemainingTooltip"
:title="timeRemainingTooltip"
:data-original-title="timeRemainingTooltip"
v-tooltip
:class="timeRemainingStatusClass"
>
<div
class="meter-container"
role="timeSpentPercent"
:aria-valuenow="timeRemainingPercent"
>
<div
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import MrWidgetAuthor from './mr_widget_author.vue';
export default {
name: 'MRWidgetAuthorTime',
name: 'MrWidgetAuthorTime',
components: {
MrWidgetAuthor,
},
directives: {
tooltip,
},
props: {
actionText: {
type: String,
......@@ -32,8 +36,7 @@
<mr-widget-author :author="author" />
<time
:title="dateTitle"
data-toggle="tooltip"
data-placement="top"
v-tooltip
data-container="body"
>
{{ dateReadable }}
......
<script>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetClosed',
components: {
mrWidgetAuthorTime,
MrWidgetAuthorTime,
statusIcon,
},
props: {
......
......@@ -4,7 +4,7 @@
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import { s__, __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
......@@ -14,7 +14,7 @@
tooltip,
},
components: {
mrWidgetAuthorTime,
MrWidgetAuthorTime,
loadingIcon,
statusIcon,
ClipboardButton,
......
......@@ -313,14 +313,6 @@ img.emoji {
margin: 5px 0;
}
.btn-sign-in {
text-shadow: none;
@include media-breakpoint-up(sm) {
margin-top: 8px;
}
}
.side-filters {
fieldset {
margin-bottom: 15px;
......
......@@ -447,12 +447,16 @@
.btn-sign-in {
background-color: $indigo-100;
color: $indigo-900;
margin-top: 3px;
font-weight: $gl-font-weight-bold;
line-height: 18px;
&:hover {
background-color: $white-light;
}
@include media-breakpoint-down(xs) {
margin-top: $gl-padding-4;
}
}
.with-performance-bar header.navbar-gitlab {
......
......@@ -58,8 +58,13 @@ table {
display: none;
}
table,
tbody,
td {
display: block;
}
td {
color: $gl-text-color-secondary;
}
......
......@@ -29,7 +29,7 @@ input[type="checkbox"]:hover {
}
.search {
margin: 4px 8px 0;
margin: 0 8px;
form {
@extend .form-control;
......
......@@ -8,6 +8,7 @@ module Ci
include Gitlab::OptimisticLocking
include Gitlab::Utils::StrongMemoize
include AtomicInternalId
include EnumWithNil
prepend ::EE::Ci::Pipeline
......@@ -65,7 +66,7 @@ module Ci
after_create :keep_around_commits, unless: :importing?
enum source: {
enum_with_nil source: {
unknown: nil,
push: 1,
web: 2,
......@@ -77,7 +78,7 @@ module Ci
chat: 8
}
enum config_source: {
enum_with_nil config_source: {
unknown_source: nil,
repository_source: 1,
auto_devops_source: 2
......@@ -616,17 +617,6 @@ module Ci
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# These methods overwrite autogenerated ones to return correct results.
def unknown?
Gitlab.rails5? ? source.nil? : super
end
def unknown_source?
Gitlab.rails5? ? config_source.nil? : super
end
private
def ci_yaml_from_repo
......
......@@ -3,6 +3,7 @@ class CommitStatus < ActiveRecord::Base
include Importable
include AfterCommitQueue
include Presentable
include EnumWithNil
self.table_name = 'ci_builds'
......@@ -39,7 +40,7 @@ class CommitStatus < ActiveRecord::Base
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
enum failure_reason: {
enum_with_nil failure_reason: {
unknown_failure: nil,
script_failure: 1,
api_failure: 2,
......@@ -190,11 +191,4 @@ class CommitStatus < ActiveRecord::Base
v =~ /\d+/ ? v.to_i : v
end
end
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
# They always return `false`.
# This method overwrites the autogenerated one to return correct result.
def unknown_failure?
Gitlab.rails5? ? failure_reason.nil? : super
end
end
module EnumWithNil
extend ActiveSupport::Concern
included do
def self.enum_with_nil(definitions)
# use original `enum` to auto-define all methods
enum(definitions)
# override auto-defined methods only for the
# key which uses nil value
definitions.each do |name, values|
next unless key_with_nil = values.key(nil)
# E.g. for enum_with_nil failure_reason: { unknown_failure: nil }
# this overrides auto-generated method `unknown_failure?`
define_method("#{key_with_nil}?") do
Gitlab.rails5? ? self[name].nil? : super()
end
# E.g. for enum_with_nil failure_reason: { unknown_failure: nil }
# this overrides auto-generated method `failure_reason`
define_method(name) do
orig = super()
return orig unless Gitlab.rails5?
return orig unless orig.nil?
self.class.public_send(name.to_s.pluralize).key(nil) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
end
......@@ -299,6 +299,7 @@ class Project < ActiveRecord::Base
validates :name, uniqueness: { scope: :namespace_id }
validates :import_url, url: { protocols: %w(http https ssh git),
allow_localhost: false,
enforce_user: true,
ports: VALID_IMPORT_PORTS }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
......
......@@ -18,7 +18,7 @@ class RemoteMirror < ActiveRecord::Base
belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true }
validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
before_save :set_new_remote_name, if: :mirror_url_changed?
......
......@@ -19,7 +19,7 @@ module Commits
new_commit = create_commit!
success(result: new_commit)
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::HooksService::PreReceiveError => ex
rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
......
......@@ -14,7 +14,7 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
rescue Gitlab::Git::HooksService::PreReceiveError => ex
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
......
......@@ -16,7 +16,7 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
rescue Gitlab::Git::HooksService::PreReceiveError => ex
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
......
......@@ -13,7 +13,7 @@ module MergeRequests
source,
merge_request.target_branch,
merge_request: merge_request)
rescue Gitlab::Git::HooksService::PreReceiveError => e
rescue Gitlab::Git::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
raise MergeError, "Something went wrong during merge: #{e.message}"
......
......@@ -81,7 +81,7 @@ module MergeRequests
message = params[:commit_message] || merge_request.merge_commit_message
repository.merge(current_user, source, merge_request, message)
rescue Gitlab::Git::HooksService::PreReceiveError => e
rescue Gitlab::Git::PreReceiveError => e
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge pre-receive hook'
rescue => e
......
......@@ -13,7 +13,7 @@ module Tags
new_tag = repository.add_tag(current_user, tag_name, target, message)
rescue Gitlab::Git::Repository::TagExistsError
return error("Tag #{tag_name} already exists")
rescue Gitlab::Git::HooksService::PreReceiveError => ex
rescue Gitlab::Git::PreReceiveError => ex
return error(ex.message)
end
......
......@@ -21,7 +21,7 @@ module Tags
else
error('Failed to remove tag')
end
rescue Gitlab::Git::HooksService::PreReceiveError => ex
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
......
......@@ -13,7 +13,7 @@ class ValidateNewBranchService < BaseService
end
success
rescue Gitlab::Git::HooksService::PreReceiveError => ex
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
end
......@@ -18,6 +18,13 @@
# This validator can also block urls pointing to localhost or the local network to
# protect against Server-side Request Forgery (SSRF), or check for the right port.
#
# The available options are:
# - protocols: Allowed protocols. Default: http and https
# - allow_localhost: Allow urls pointing to localhost. Default: true
# - allow_local_network: Allow urls pointing to private network addresses. Default: true
# - ports: Allowed ports. Default: all.
# - enforce_user: Validate user format. Default: false
#
# Example:
# class User < ActiveRecord::Base
# validates :personal_url, url: { allow_localhost: false, allow_local_network: false}
......@@ -35,7 +42,7 @@ class UrlValidator < ActiveModel::EachValidator
if value.present?
value.strip!
else
record.errors.add(attribute, "must be a valid URL")
record.errors.add(attribute, 'must be a valid URL')
end
Gitlab::UrlBlocker.validate!(value, blocker_args)
......@@ -51,7 +58,8 @@ class UrlValidator < ActiveModel::EachValidator
protocols: DEFAULT_PROTOCOLS,
ports: [],
allow_localhost: true,
allow_local_network: true
allow_local_network: true,
enforce_user: false
}
end
......@@ -64,7 +72,7 @@ class UrlValidator < ActiveModel::EachValidator
end
def blocker_args
current_options.slice(:allow_localhost, :allow_local_network, :protocols, :ports).tap do |args|
current_options.slice(*default_options.keys).tap do |args|
if allow_setting_local_requests?
args[:allow_localhost] = args[:allow_local_network] = true
end
......
......@@ -43,8 +43,6 @@
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
-# EE-specific start
= render 'ci/variables/environment_scope', form_field: form_field, variable: variable
-# EE-specific end
= render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable
%button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
......@@ -61,7 +61,7 @@
- if header_link?(:sign_in)
%li.nav-item
%div
= link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'nav-link btn btn-sign-in'
= link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
%button.navbar-toggler.d-block.d-sm-none{ type: 'button' }
%span.sr-only Toggle navigation
......
#js-pipeline-header-vue.pipeline-header-container
- sast_artifact = @pipeline.sast_artifact
- dependency_artifact = @pipeline.dependency_scanning_artifact
- dast_artifact = @pipeline.dast_artifact
- sast_container_artifact = @pipeline.sast_container_artifact
- if @commit.present?
.commit-box
......@@ -36,5 +38,5 @@
= link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
- if sast_artifact || dependency_artifact
- if sast_artifact || dependency_artifact || dast_artifact || sast_container_artifact
.js-sast-summary
- expose_sast_data = @pipeline.expose_sast_data?
- expose_dependency_data = @pipeline.expose_dependency_scanning_data?
- expose_dast_data = @pipeline.expose_dast_data?
- expose_sast_container_data = @pipeline.expose_sast_container_data?
- expose_container_scanning_data = @pipeline.expose_container_scanning_data?
- blob_path = project_blob_path(@project, @pipeline.sha)
.tabs-holder
......@@ -84,13 +87,17 @@
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
- if expose_sast_data || expose_dependency_data
- if expose_sast_data || expose_dependency_data || expose_dast_data || expose_sast_container_data || expose_container_scanning_data
#js-tab-security.build-security.tab-pane
#js-security-report-app{ data: { endpoint: expose_sast_data ? sast_artifact_url(@pipeline) : nil,
blob_path: blob_path,
dependency_scanning_endpoint: expose_dependency_data ? dependency_scanning_artifact_url(@pipeline) : nil,
dast_endpoint: expose_dast_data ? dast_artifact_url(@pipeline) : nil,
sast_container_endpoint: expose_sast_container_data ? sast_container_artifact_url(@pipeline) : (expose_container_scanning_data ? container_scanning_artifact_url(@pipeline) : nil),
pipeline_id: @pipeline.id,
vulnerability_feedback_path: project_vulnerability_feedback_index_path(@project),
vulnerability_feedback_help_path: help_page_path("user/project/merge_requests/index", anchor: "interacting-with-security-reports-ultimate"),
sast_help_path: help_page_path('user/project/merge_requests/sast'),
dependency_scanning_help_path: help_page_path('user/project/merge_requests/dependency_scanning')} }
dependency_scanning_help_path: help_page_path('user/project/merge_requests/dependency_scanning'),
dast_help_path: help_page_path('user/project/merge_requests/dast'),
sast_container_help_path: help_page_path('user/project/merge_requests/sast_container')} }
= render 'projects/commits/commit', project: commit.project, commit: commit, ref: nil, show_project_name: true
= render 'projects/commits/commit', project: commit.project, commit: commit, ref: nil, show_project_name: @project.nil?
......@@ -18,8 +18,6 @@
= import_will_timeout_message(ci_cd_only)
%li
= import_svn_message(ci_cd_only)
%li
The Git LFS objects will be ignored.
- unless ci_cd_only
%li
- ssh_link = link_to _('here'), help_page_path('/workflow/repository_mirroring.md', anchor: 'ssh-authentication')
......
---
title: Fix overflowing Failed Jobs table in sm viewports on IE11
merge_request:
author:
type: fixed
---
title: Avoid checking the user format in every url validation
merge_request: 19575
author:
type: changed
---
title: Move some Gitaly RPC's to opt-out
merge_request: 19591
author:
type: other
---
title: Hide project name if searching against a project
merge_request: 19595
author:
type: changed
---
title: Use Tooltip component in MrWidgetAuthorTime vue comonent
merge_request: 19635
author: George Tsiolis
type: performance
......@@ -41,7 +41,12 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses) #:nodoc:
attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses]
# In Rails 5 the second last attribute is newly `:comment`
attrs.insert(-2, :comment) if Gitlab.rails5?
class IndexDefinition < Struct.new(*attrs) #:nodoc:
end
end
end
......
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
......@@ -232,9 +231,7 @@ module.exports = {
new VueLoaderPlugin(),
// automatically configure monaco editor web workers
new MonacoWebpackPlugin({
features: [],
}),
new MonacoWebpackPlugin(),
// prevent pikaday from including moment.js
new webpack.IgnorePlugin(/moment/, /pikaday/),
......@@ -302,7 +299,7 @@ module.exports = {
inline: DEV_SERVER_LIVERELOAD,
},
devtool: IS_PRODUCTION ? 'nosources-source-map' : 'cheap-module-eval-source-map',
devtool: IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map',
// sqljs requires fs
node: { fs: 'empty' },
......
......@@ -301,9 +301,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-8-stable gitlab
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-0-stable gitlab
**Note:** You can change `10-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
**Note:** You can change `11-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
......
This diff is collapsed.
# From Community Edition 11.0 to Enterprise Edition 11.0
This guide assumes you have a correctly configured and tested installation of
GitLab Community Edition 11.0. If you run into any trouble or if you have any
questions please contact us at [support@gitlab.com].
### 0. Backup
Make a backup just in case something goes wrong:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
For installations using MySQL, this may require granting "LOCK TABLES"
privileges to the GitLab user on the database version.
### 1. Stop server
```bash
sudo service gitlab stop
```
### 2. Get the EE code
```bash
cd /home/git/gitlab
sudo -u git -H git remote add -f ee https://gitlab.com/gitlab-org/gitlab-ee.git
sudo -u git -H git checkout 11-0-stable-ee
```
### 3. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 4. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
### 5. Check application status
Check if GitLab and its environment are configured correctly:
```bash
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
```
To make sure you didn't miss anything run a more thorough check with:
```bash
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
If all items are green, then congratulations upgrade complete!
## Things went south? Revert to previous version (Community Edition 11.0)
### 1. Revert the code to the previous version
```bash
cd /home/git/gitlab
sudo -u git -H git checkout 11-0-stable
```
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
[support@gitlab.com]: mailto:support@gitlab.com
......@@ -313,9 +313,77 @@ mitigated by reducing the mirroring delay by using a Push event webhook to
trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited
to once per minute when only push mirroring protected branches.
It may be possible to implement a locking mechanism using the server-side
`pre-receive` hook to prevent the race condition. Read about [configuring
custom Git hooks][hooks] on the GitLab server.
### Preventing conflicts using a `pre-receive` hook
> **Warning:** The solution proposed will negatively impact the performance of
> Git push operations because they will be proxied to the upstream Git
> repository.
A server-side `pre-receive` hook can be used to prevent the race condition
described above by only accepting the push after first pushing the commit to
the upstream Git repository. In this configuration one Git repository acts as
the authoritative upstream, and the other as downstream. The `pre-recieve` hook
will be installed on the downstream repository.
Read about [configuring custom Git hooks][hooks] on the GitLab server.
A sample `pre-recieve` hook is provided below.
```bash
#!/usr/bin/env bash
# --- Assume only one push mirror target
# Push mirroring remotes are named `remote_mirror_<id>`, this finds the first remote and uses that.
TARGET_REPO=$(git remote | grep -m 1 remote_mirror)
proxy_push()
{
# --- Arguments
OLDREV=$(git rev-parse $1)
NEWREV=$(git rev-parse $2)
REFNAME="$3"
# --- Pattern of branches to proxy pushes
whitelisted=$(expr "$branch" : "\(master\)")
case "$refname" in
refs/heads/*)
branch=$(expr "$refname" : "refs/heads/\(.*\)")
if [ "$whitelisted" = "$branch" ]; then
error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)"
fail=$?
if [ "$fail" != "0" ]; then
echo >&2 ""
echo >&2 " Error: updates were rejected by upstream server"
echo >&2 " This is usually caused by another repository pushing changes"
echo >&2 " to the same ref. You may want to first integrate remote changes"
echo >&2 ""
return
fi
fi
;;
esac
}
# Allow dual mode: run from the command line just like the update hook, or
# if no arguments are given then run as a hook script
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# Output to the terminal in command line mode - if someone wanted to
# resend an email; they could redirect the output to sendmail
# themselves
PAGER= proxy_push $2 $3 $1
else
# Push is proxied upstream one ref at a time. Because of this it is possible
# for some refs to succeed, and others to fail. This will result in a failed
# push.
while read oldrev newrev refname
do
proxy_push $oldrev $newrev $refname
done
fi
```
### Mirroring with Perforce via GitFusion
......@@ -335,6 +403,17 @@ Perforce will reject any pushes that rewrite history. It is recommended that
only the fewest number of branches are mirrored due to the performance
limitations of GitFusion.
When configuring mirroring with Perforce via GitFusion, the following GitFusion
settings are recommended:
- `change-pusher` should be disabled, otherwise every commit will be rewritten
as being committed by the mirroring account, rather than being mapped to
existing Perforce users or the `unknown_git` user.
- `unknown_git` user will be used as the commit author if the GitLab user does
not exist in Perforce.
Read about [GitFusion settings on Perforce.com][gitfusion-settings].
[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551
[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117
......@@ -349,3 +428,4 @@ limitations of GitFusion.
[webhook]: ../user/project/integrations/webhooks.md#push-events
[pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project
[perforce]: ../user/project/import/perforce.md
[gitfusion-settings]: https://www.perforce.com/perforce/doc.current/manuals/git-fusion/Content/Git-Fusion/section_zdp_zz1_3l.html
......@@ -12,13 +12,19 @@ export default {
LoadingIcon,
},
computed: {
...mapState(['sast', 'dependencyScanning']),
...mapState(['sast', 'dependencyScanning', 'dast', 'sastContainer']),
sastLink() {
return this.link(this.sast.newIssues.length);
},
dependencyScanningLink() {
return this.link(this.dependencyScanning.newIssues.length);
},
dastLink() {
return this.link(this.dast.newIssues.length);
},
sastContainerLink() {
return this.link(this.sastContainer.newIssues.length);
},
sastIcon() {
return this.statusIcon(this.hasSastError, this.sast.newIssues.length);
},
......@@ -28,24 +34,48 @@ export default {
this.dependencyScanning.newIssues.length,
);
},
dastIcon() {
return this.statusIcon(this.hasDastError, this.dast.newIssues.length);
},
sastContainerIcon() {
return this.statusIcon(this.hasSastContainerError, this.sastContainer.newIssues.length);
},
hasSast() {
return this.sast.paths.head !== null;
},
hasDependencyScanning() {
return this.dependencyScanning.paths.head !== null;
},
hasDast() {
return this.dast.paths.head !== null;
},
hasSastContainer() {
return this.sastContainer.paths.head !== null;
},
isLoadingSast() {
return this.sast.isLoading;
},
isLoadingDependencyScanning() {
return this.dependencyScanning.isLoading;
},
isLoadingDast() {
return this.dast.isLoading;
},
isLoadingSastContainer() {
return this.sastContainer.isLoading;
},
hasSastError() {
return this.sast.hasError;
},
hasDependencyScanningError() {
return this.dependencyScanning.hasError;
},
hasDastError() {
return this.dast.hasError;
},
hasSastContainerError() {
return this.sastContainer.hasError;
},
},
methods: {
openTab() {
......@@ -117,7 +147,7 @@ export default {
v-if="hasDependencyScanning"
>
<loading-icon
v-if="dependencyScanning.isLoading"
v-if="isLoadingDependencyScanning"
/>
<ci-icon
v-else
......@@ -146,5 +176,73 @@ export default {
</template>
</span>
</div>
<div
class="well-segment flex js-sast-container-summary"
v-if="hasSastContainer"
>
<loading-icon
v-if="isLoadingSastContainer"
/>
<ci-icon
v-else
:status="sastContainerIcon"
class="flex flex-align-self-center"
/>
<span
class="prepend-left-10 flex flex-align-self-center"
>
<template v-if="hasSastContainerError">
{{ s__('ciReport|Container scanning resulted in error while loading results') }}
</template>
<template v-else-if="isLoadingSastContainer">
{{ s__('ciReport|Container scanning is loading') }}
</template>
<template v-else>
{{ s__('ciReport|Container scanning detected') }}
<button
type="button"
class="btn-link btn-blank prepend-left-5"
@click="openTab"
>
{{ sastContainerLink }}
</button>
</template>
</span>
</div>
<div
class="well-segment flex js-dast-summary"
v-if="hasDast"
>
<loading-icon
v-if="isLoadingDast"
/>
<ci-icon
v-else
:status="dastIcon"
class="flex flex-align-self-center"
/>
<span
class="prepend-left-10 flex flex-align-self-center"
>
<template v-if="hasDastError">
{{ s__('ciReport|DAST resulted in error while loading results') }}
</template>
<template v-else-if="isLoadingDast">
{{ s__('ciReport|DAST is loading') }}
</template>
<template v-else>
{{ s__('ciReport|DAST detected') }}
<button
type="button"
class="btn-link btn-blank prepend-left-5"
@click="openTab"
>
{{ dastLink }}
</button>
</template>
</span>
</div>
</div>
</template>
......@@ -161,7 +161,7 @@ export default {
<a
v-if="showApprovalDocLink"
:href="mr.approvalsHelpPath"
:data-title="__('About this feature')"
:title="__('About this feature')"
data-placement="bottom"
target="_blank"
rel="noopener noreferrer nofollow"
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
export default {
components: {
statusIcon,
},
directives: {
tooltip,
},
props: {
mr: {
type: Object,
......@@ -25,9 +29,9 @@ export default {
</span>
<a
:href="mr.geoSecondaryHelpPath"
data-title="About this feature"
data-toggle="tooltip"
:title="__('About this feature')"
data-placement="bottom"
v-tooltip
target="_blank"
rel="noopener noreferrer nofollow"
data-container="body"
......
......@@ -28,6 +28,12 @@ export default {
<template v-if="issue.severity && issue.confidence">
{{ issue.severity }} ({{ issue.confidence }}):
</template>
<template v-else-if="issue.severity">
{{ issue.severity }}:
</template>
<template v-else-if="issue.confidence">
({{ issue.confidence }}):
</template>
<template v-else-if="issue.priority">{{ issue.priority }}:</template>
<modal-open-name :issue="issue" />
......
......@@ -2,7 +2,7 @@
import { mapActions, mapState } from 'vuex';
import { s__, sprintf, n__ } from '~/locale';
import createFlash from '~/flash';
import { SAST } from './store/constants';
import { SAST, DAST, SAST_CONTAINER } from './store/constants';
import ReportSection from './components/report_section.vue';
import IssueModal from './components/modal.vue';
import mixin from './mixins/security_report_mixin';
......@@ -24,6 +24,16 @@ export default {
required: false,
default: null,
},
dastHeadPath: {
type: String,
required: false,
default: null,
},
sastContainerHeadPath: {
type: String,
required: false,
default: null,
},
dependencyScanningHeadPath: {
type: String,
required: false,
......@@ -34,6 +44,16 @@ export default {
required: false,
default: null,
},
sastContainerHelpPath: {
type: String,
required: false,
default: '',
},
dastHelpPath: {
type: String,
required: false,
default: '',
},
dependencyScanningHelpPath: {
type: String,
required: false,
......@@ -56,8 +76,10 @@ export default {
},
},
sast: SAST,
dast: DAST,
sastContainer: SAST_CONTAINER,
computed: {
...mapState(['sast', 'dependencyScanning']),
...mapState(['sast', 'dependencyScanning', 'sastContainer', 'dast']),
sastText() {
return this.summaryTextBuilder('SAST', this.sast.newIssues.length);
......@@ -69,6 +91,14 @@ export default {
this.dependencyScanning.newIssues.length,
);
},
sastContainerText() {
return this.summaryTextBuilder('Container scanning', this.sastContainer.newIssues.length);
},
dastText() {
return this.summaryTextBuilder('DAST', this.dast.newIssues.length);
},
},
created() {
// update the store with the received props
......@@ -98,6 +128,30 @@ export default {
createFlash(s__('ciReport|There was an error loading dependency scanning report')),
);
}
if (this.sastContainerHeadPath) {
this.setSastContainerHeadPath(this.sastContainerHeadPath);
this.fetchSastContainerReports()
.then(() => {
this.$emit('updateBadgeCount', this.sastContainer.newIssues.length);
})
.catch(() =>
createFlash(s__('ciReport|There was an error loading container scanning report')),
);
}
if (this.dastHeadPath) {
this.setDastHeadPath(this.dastHeadPath);
this.fetchDastReports()
.then(() => {
this.$emit('updateBadgeCount', this.dast.newIssues.length);
})
.catch(() =>
createFlash(s__('ciReport|There was an error loading DAST report')),
);
}
},
methods: {
......@@ -105,8 +159,12 @@ export default {
'setHeadBlobPath',
'setSastHeadPath',
'setDependencyScanningHeadPath',
'setSastContainerHeadPath',
'setDastHeadPath',
'fetchSastReports',
'fetchDependencyScanningReports',
'fetchSastContainerReports',
'fetchDastReports',
'setVulnerabilityFeedbackPath',
'setVulnerabilityFeedbackHelpPath',
'setPipelineId',
......@@ -164,6 +222,32 @@ export default {
:popover-options="dependencyScanningPopover"
/>
<report-section
v-if="sastContainerHeadPath"
class="js-dependency-scanning-widget split-report-section"
:type="$options.sastContainer"
:status="checkReportStatus(sastContainer.isLoading, sastContainer.hasError)"
:loading-text="translateText('Container scanning').loading"
:error-text="translateText('Container scanning').error"
:success-text="sastContainerText"
:unresolved-issues="sastContainer.newIssues"
:has-issues="sastContainer.newIssues.length > 0"
:popover-options="sastContainerPopover"
/>
<report-section
v-if="dastHeadPath"
class="js-dast-widget split-report-section"
:type="$options.dast"
:status="checkReportStatus(dast.isLoading, dast.hasError)"
:loading-text="translateText('DAST').loading"
:error-text="translateText('DAST').error"
:success-text="dastText"
:unresolved-issues="dast.newIssues"
:has-issues="dast.newIssues.length > 0"
:popover-options="dastPopover"
/>
<issue-modal />
</div>
</template>
......@@ -170,7 +170,7 @@ export default {
state.dast.isLoading = false;
state.summaryCounts.added += newIssues.length;
state.summaryCounts.fixed += resolvedIssues.length;
} else if (reports.head && !reports.base) {
} else if (reports.head && reports.head.site && !reports.base) {
const newIssues = parseDastIssues(reports.head.site.alerts, reports.enrichData);
state.dast.newIssues = newIssues;
......
......@@ -40,6 +40,25 @@ module EE
path: Ci::Build::DEPENDENCY_SCANNING_FILE)
end
# sast_container_artifact_url is deprecated and replaced with container_scanning_artifact_url (#5778)
def sast_container_artifact_url(pipeline)
raw_project_build_artifacts_url(pipeline.project,
pipeline.sast_container_artifact,
path: Ci::Build::SAST_CONTAINER_FILE)
end
def container_scanning_artifact_url(pipeline)
raw_project_build_artifacts_url(pipeline.project,
pipeline.container_scanning_artifact,
path: Ci::Build::CONTAINER_SCANNING_FILE)
end
def dast_artifact_url(pipeline)
raw_project_build_artifacts_url(pipeline.project,
pipeline.dast_artifact,
path: Ci::Build::DAST_FILE)
end
def license_management_artifact_url(pipeline)
raw_project_build_artifacts_url(pipeline.project,
pipeline.license_management_artifact,
......
......@@ -11,6 +11,24 @@ module EE
noteable.is_a?(Epic)
end
# Remove with https://gitlab.com/gitlab-org/gitlab-ee/issues/6347
def note
raw_note = super
return raw_note unless system? && system_note_metadata&.action == 'weight'
raw_note.delete(',')
end
# Remove with https://gitlab.com/gitlab-org/gitlab-ee/issues/6347
def note_html
raw_note_html = super
return raw_note_html unless system? && system_note_metadata&.action == 'weight'
raw_note_html.delete(',')
end
override :for_project_noteable?
def for_project_noteable?
!for_epic? && super
......
......@@ -12,13 +12,19 @@ module EE
# and clean up where we can.
if project&.destroyed?
mirror_cleanup(project)
log_geo_event(project)
log_audit_event(project)
end
succeeded
end
override :log_destroy_event
def log_destroy_event
super
log_geo_event(project)
log_audit_event(project)
end
def mirror_cleanup(project)
return unless project.mirror?
......
......@@ -21,7 +21,7 @@ module EE
# Returns the created Note object
def change_weight_note(noteable, project, author)
body = noteable.weight ? "changed weight to **#{noteable.weight}**," : 'removed the weight'
body = noteable.weight ? "changed weight to **#{noteable.weight}**" : 'removed the weight'
create_note(NoteSummary.new(noteable, project, author, body, action: 'weight'))
end
......
- namespace = local_assigns.fetch(:namespace)
- return unless Gitlab::CurrentSettings.should_check_namespace_plan? && namespace.plan.present
- return unless Gitlab::CurrentSettings.should_check_namespace_plan? && namespace.plan.present?
%span.plan-badge.has-tooltip{ data: { plan: namespace.plan.name }, title: "#{namespace.plan.title} Plan" }
= custom_icon('icon_premium')
---
title: Render container scanning and dast reports in pipeline view
merge_request:
author:
type: added
---
title: Remove LFS object warning from import UI
merge_request: 6083
author:
type: fixed
---
title: Remove the comma from the weight system notes
merge_request: 5854
author:
type: changed
---
title: Log audit and Geo events within a project destroy transaction
merge_request: 6059
author:
type: fixed
---
title: Use tooltip component in MrWidgetSecondaryGeoNode vue component
merge_request: 6078
author: George Tsiolis
type: performance
......@@ -22,7 +22,7 @@ describe Boards::IssuesController do
end
describe 'GET index' do
let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
let(:johndoe) { create(:user, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
context 'with invalid board id' do
it 'returns a not found 404 response' do
......
......@@ -4,7 +4,7 @@ describe Groups::AvatarsController do
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) }
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
before do
group.add_owner(user)
......
require 'spec_helper'
describe Gitlab::Geo::FileTransfer do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:user) { create(:user, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') }
subject { described_class.new(:file, upload) }
......
require 'spec_helper'
describe EE::Note do
# Remove with https://gitlab.com/gitlab-org/gitlab-ee/issues/6347
describe "#note and #note_html overrides for weight" do
using RSpec::Parameterized::TableSyntax
where(:system, :action, :result) do
false | nil | 'this, had, some, commas, originally'
true | nil | 'this, had, some, commas, originally'
true | 'relate' | 'this, had, some, commas, originally'
true | 'weight' | 'this had some commas originally'
end
with_them do
let(:note) { create(:note, system: system, note: 'this, had, some, commas, originally') }
before do
create(:system_note_metadata, action: action, note: note) if action
end
it 'returns the right raw note' do
expect(note.note).to eq(result)
end
it 'returns the right HTML' do
expect(note.note_html).to eq("<p dir=\"auto\">#{result}</p>")
end
end
end
end
......@@ -124,7 +124,7 @@ describe GeoNodeStatus, :geo do
# can't see changes inside a transaction of a different connection.
describe '#attachments_synced_count', :delete do
it 'only counts successful syncs' do
create_list(:user, 3, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
create_list(:user, 3, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png'))
uploads = Upload.all.pluck(:id)
create(:geo_file_registry, :avatar, file_id: uploads[0])
......@@ -135,7 +135,7 @@ describe GeoNodeStatus, :geo do
end
it 'does not count synced files that were replaced' do
user = create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
user = create(:user, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png'))
expect(subject.attachments_count).to eq(1)
expect(subject.attachments_synced_count).to eq(0)
......@@ -148,7 +148,7 @@ describe GeoNodeStatus, :geo do
expect(subject.attachments_count).to eq(1)
expect(subject.attachments_synced_count).to eq(1)
user.update(avatar: fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg'))
user.update(avatar: fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg'))
subject = described_class.current_node_status
......@@ -169,7 +169,7 @@ describe GeoNodeStatus, :geo do
# can't see changes inside a transaction of a different connection.
describe '#attachments_synced_missing_on_primary_count', :delete do
it 'only counts successful syncs' do
create_list(:user, 3, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
create_list(:user, 3, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png'))
uploads = Upload.all.pluck(:id)
create(:geo_file_registry, :avatar, file_id: uploads[0], missing_on_primary: true)
......@@ -196,7 +196,7 @@ describe GeoNodeStatus, :geo do
end
describe '#attachments_synced_in_percentage', :delete do
let(:avatar) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
let(:avatar) { fixture_file_upload('spec/fixtures/dk.png') }
let(:upload_1) { create(:upload, model: group, path: avatar) }
let(:upload_2) { create(:upload, model: project_1, path: avatar) }
......
......@@ -70,7 +70,7 @@ describe API::Geo do
end
describe 'GET /geo/transfers/avatar/1' do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:user) { create(:user, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') }
let(:transfer) { Gitlab::Geo::FileTransfer.new(:avatar, upload) }
let(:req_header) { Gitlab::Geo::TransferRequest.new(transfer.request_data).headers }
......@@ -116,7 +116,7 @@ describe API::Geo do
before do
allow_any_instance_of(Gitlab::Geo::TransferRequest).to receive(:requesting_node).and_return(secondary_node)
FileUploader.new(project).store!(fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png'))
FileUploader.new(project).store!(fixture_file_upload('spec/fixtures/dk.png', 'image/png'))
end
it 'responds with 401 with invalid auth header' do
......
......@@ -5,7 +5,7 @@ describe API::ProjectImport do
let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
let(:user) { create(:user) }
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:namespace) { create(:group) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
......
......@@ -6,7 +6,7 @@ describe Issuable::CommonSystemNotesService do
let(:issuable) { create(:issue) }
describe '#execute' do
it_behaves_like 'system note creation', { weight: 5 }, 'changed weight to **5**,'
it_behaves_like 'system note creation', { weight: 5 }, 'changed weight to **5**'
context 'when issuable is an epic' do
let(:timestamp) { Time.now }
......
......@@ -138,7 +138,7 @@ describe Geo::FileRegistryRemovalService do
context 'with namespace_file' do
set(:group) { create(:group) }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
let(:file) { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
let!(:upload) do
NamespaceFileUploader.new(group).store!(file)
Upload.find_by(model: group, uploader: NamespaceFileUploader)
......@@ -161,7 +161,7 @@ describe Geo::FileRegistryRemovalService do
context 'with personal_file' do
let(:snippet) { create(:personal_snippet) }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
let(:file) { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
let!(:upload) do
PersonalFileUploader.new(snippet).store!(file)
Upload.find_by(model: snippet, uploader: PersonalFileUploader)
......
......@@ -5,7 +5,7 @@ describe Geo::FileUploadService do
describe '#execute' do
context 'user avatar' do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:user) { create(:user, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: user, uploader: 'AvatarUploader') }
let(:params) { { id: upload.id, type: 'avatar' } }
let(:file_transfer) { Gitlab::Geo::FileTransfer.new(:avatar, upload) }
......@@ -29,7 +29,7 @@ describe Geo::FileUploadService do
end
context 'group avatar' do
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: group, uploader: 'AvatarUploader') }
let(:params) { { id: upload.id, type: 'avatar' } }
let(:file_transfer) { Gitlab::Geo::FileTransfer.new(:avatar, upload) }
......@@ -53,7 +53,7 @@ describe Geo::FileUploadService do
end
context 'project avatar' do
let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')) }
let(:project) { create(:project, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:upload) { Upload.find_by(model: project, uploader: 'AvatarUploader') }
let(:params) { { id: upload.id, type: 'avatar' } }
let(:file_transfer) { Gitlab::Geo::FileTransfer.new(:avatar, upload) }
......@@ -107,7 +107,7 @@ describe Geo::FileUploadService do
let(:file_transfer) { Gitlab::Geo::FileTransfer.new(:file, upload) }
let(:transfer_request) { Gitlab::Geo::TransferRequest.new(file_transfer.request_data) }
let(:req_header) { transfer_request.headers['Authorization'] }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
let(:file) { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
before do
FileUploader.new(project).store!(file)
......@@ -136,7 +136,7 @@ describe Geo::FileUploadService do
let(:file_transfer) { Gitlab::Geo::FileTransfer.new(:file, upload) }
let(:transfer_request) { Gitlab::Geo::TransferRequest.new(file_transfer.request_data) }
let(:req_header) { transfer_request.headers['Authorization'] }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
let(:file) { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
before do
NamespaceFileUploader.new(group).store!(file)
......
require 'spec_helper'
describe Groups::ParticipantsService do
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png')) }
let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
let(:user) { create(:user) }
let(:epic) { create(:epic, group: group, author: user) }
......
......@@ -36,11 +36,13 @@ describe Projects::DestroyService do
it 'logs an event to the Geo event log' do
# Run Sidekiq immediately to check that renamed repository will be removed
Sidekiq::Testing.inline! do
expect(subject).to receive(:log_destroy_event).and_call_original
expect { subject.execute }.to change(Geo::RepositoryDeletedEvent, :count).by(1)
end
end
it 'does not log event to the Geo log if project deletion fails' do
expect(subject).to receive(:log_destroy_event).and_call_original
expect_any_instance_of(Project)
.to receive(:destroy!).and_raise(StandardError.new('Other error message'))
......@@ -80,6 +82,7 @@ describe Projects::DestroyService do
end
it 'logs an audit event' do
expect(subject).to receive(:log_destroy_event).and_call_original
expect { subject.execute }.to change(AuditEvent, :count)
end
end
......
......@@ -47,7 +47,7 @@ describe SystemNoteService do
end
it 'sets the note text' do
expect(subject.note).to eq "changed weight to **4**,"
expect(subject.note).to eq "changed weight to **4**"
end
end
......
require 'rails_helper'
describe 'admin/users/index' do
it 'includes "Send email to users" link' do
let(:should_check_namespace_plan) { false }
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?)
.and_return(should_check_namespace_plan)
allow(view).to receive(:container_class).and_return('ignored')
create(:user) # to have at least one usser
assign(:users, User.all.page(1))
render
end
it 'includes "Send email to users" link' do
expect(rendered).to have_link 'Send email to users', href: admin_email_path
end
context 'when Gitlab::CurrentSettings.should_check_namespace_plan is true' do
let(:should_check_namespace_plan) { true }
it 'includes "Send email to users" link' do
expect(rendered).to have_link 'Send email to users', href: admin_email_path
end
end
end
......@@ -321,7 +321,7 @@ describe Geo::FileDownloadDispatchWorker, :geo do
result_object = double(:result, success: true, bytes_downloaded: 100, primary_missing_file: false)
allow_any_instance_of(::Gitlab::Geo::Transfer).to receive(:download_from_primary).and_return(result_object)
avatar = fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'))
avatar = fixture_file_upload('spec/fixtures/dk.png')
create_list(:lfs_object, 2, :with_file)
create_list(:user, 2, avatar: avatar)
create_list(:note, 2, :with_attachment)
......@@ -375,7 +375,7 @@ describe Geo::FileDownloadDispatchWorker, :geo do
end
it 'does not perform Geo::FileDownloadWorker for upload objects that do not belong to selected namespaces to replicate' do
avatar = fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'))
avatar = fixture_file_upload('spec/fixtures/dk.png')
avatar_in_synced_group = create(:upload, model: synced_group, path: avatar)
create(:upload, model: create(:group), path: avatar)
avatar_in_project_in_synced_group = create(:upload, model: project_in_synced_group, path: avatar)
......
......@@ -7,9 +7,7 @@ module Gitlab
end
def value
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
@value ||= count_commits
end
@value ||= count_commits
end
private
......@@ -21,19 +19,11 @@ module Gitlab
def count_commits
return unless ref
repository = @project.repository.raw_repository
sha = @project.repository.commit(ref).sha
cmd = %W(git --git-dir=#{repository.path} log)
cmd << '--format=%H'
cmd << "--after=#{@from.iso8601}"
cmd << sha
output, status = Gitlab::Popen.popen(cmd)
raise IOError, output unless status.zero?
gitaly_commit_client.commit_count(ref, after: @from)
end
output.lines.count
def gitaly_commit_client
Gitlab::GitalyClient::CommitService.new(@project.repository.raw_repository)
end
def ref
......
......@@ -22,7 +22,7 @@ module Gitlab
private
def load_blame
raw_output = @repo.gitaly_migrate(:blame) do |is_enabled|
raw_output = @repo.gitaly_migrate(:blame, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
load_blame_by_gitaly
else
......
......@@ -20,7 +20,7 @@ module Gitlab
end
result[:newrev]
rescue Gitlab::Git::HooksService::PreReceiveError => e
rescue Gitlab::Git::PreReceiveError => e
message = "Custom Hook failed: #{e.message}"
raise Gitlab::Git::Wiki::OperationError, message
end
......
......@@ -11,7 +11,7 @@ module Gitlab
def initialize(name, repository)
@name = name
@repository = repository
@path = File.join(repo_path.strip, 'hooks', name)
@path = File.join(repo_path, 'hooks', name)
end
def repo_path
......@@ -95,13 +95,13 @@ module Gitlab
args = [ref, oldrev, newrev]
stdout, stderr, status = Open3.capture3(env, path, *args, options)
[status.success?, Gitlab::Utils.nlbr(stderr.presence || stdout)]
[status.success?, stderr.presence || stdout]
end
def retrieve_error_message(stderr, stdout)
err_message = stderr.read
err_message = err_message.blank? ? stdout.read : err_message
Gitlab::Utils.nlbr(err_message)
err_message
end
end
end
......
module Gitlab
module Git
class HooksService
PreReceiveError = Class.new(StandardError)
attr_accessor :oldrev, :newrev, :ref
def execute(pusher, repository, oldrev, newrev, ref)
......
module Gitlab
module Git
#
# PreReceiveError is special because its message gets displayed to users
# in the web UI. To prevent XSS we sanitize the message on
# initialization.
class PreReceiveError < StandardError
def initialize(msg = '')
super(nlbr(msg))
end
private
# In gitaly-ruby we override this method to do nothing, so that
# sanitization happens in gitlab-rails only.
def nlbr(str)
Gitlab::Utils.nlbr(str)
end
end
end
end
......@@ -533,7 +533,7 @@ module Gitlab
def count_commits(options)
count_commits_options = process_count_commits_options(options)
gitaly_migrate(:count_commits) do |is_enabled|
gitaly_migrate(:count_commits, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
count_commits_by_gitaly(count_commits_options)
else
......@@ -738,7 +738,7 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/330
def commit_count(ref)
gitaly_migrate(:commit_count) do |is_enabled|
gitaly_migrate(:commit_count, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_commit_client.commit_count(ref)
else
......
......@@ -20,7 +20,7 @@ module Gitlab
response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_tag, request)
if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
raise Gitlab::Git::PreReceiveError, pre_receive_error
end
end
......@@ -35,7 +35,7 @@ module Gitlab
response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request)
if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
raise Gitlab::Git::PreReceiveError, pre_receive_error
elsif response.exists
raise Gitlab::Git::Repository::TagExistsError
end
......@@ -56,7 +56,7 @@ module Gitlab
:user_create_branch, request)
if response.pre_receive_error.present?
raise Gitlab::Git::HooksService::PreReceiveError.new(response.pre_receive_error)
raise Gitlab::Git::PreReceiveError.new(response.pre_receive_error)
end
branch = response.branch
......@@ -76,7 +76,7 @@ module Gitlab
response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_branch, request)
if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
raise Gitlab::Git::PreReceiveError, pre_receive_error
end
end
......@@ -106,7 +106,7 @@ module Gitlab
second_response = response_enum.next
if second_response.pre_receive_error.present?
raise Gitlab::Git::HooksService::PreReceiveError, second_response.pre_receive_error
raise Gitlab::Git::PreReceiveError, second_response.pre_receive_error
end
branch_update = second_response.branch_update
......@@ -175,7 +175,7 @@ module Gitlab
)
if response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error
raise Gitlab::Git::PreReceiveError, response.pre_receive_error
elsif response.git_error.presence
raise Gitlab::Git::Repository::GitError, response.git_error
else
......@@ -242,7 +242,7 @@ module Gitlab
:user_commit_files, req_enum, remote_storage: start_repository.storage)
if (pre_receive_error = response.pre_receive_error.presence)
raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
raise Gitlab::Git::PreReceiveError, pre_receive_error
end
if (index_error = response.index_error.presence)
......@@ -280,7 +280,7 @@ module Gitlab
def handle_cherry_pick_or_revert_response(response)
if response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error
raise Gitlab::Git::PreReceiveError, response.pre_receive_error
elsif response.commit_error.presence
raise Gitlab::Git::CommitError, response.commit_error
elsif response.create_tree_error.presence
......
......@@ -5,7 +5,7 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
def validate!(url, allow_localhost: false, allow_local_network: true, ports: [], protocols: [])
def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
return true if url.nil?
begin
......@@ -20,7 +20,7 @@ module Gitlab
port = uri.port || uri.default_port
validate_protocol!(uri.scheme, protocols)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user)
validate_user!(uri.user) if enforce_user
validate_hostname!(uri.hostname)
begin
......
......@@ -6,6 +6,7 @@ module QA::Page
view 'app/views/shared/builds/_build_output.html.haml' do
element :build_output, '.js-build-output'
element :loading_animation, '.js-build-refresh'
end
view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do
......@@ -20,6 +21,10 @@ module QA::Page
find('.ci-status').text == PASSED_STATUS
end
def trace_loading?
has_css?('.js-build-refresh')
end
# Reminder: You may wish to wait for a particular job status before checking output
def output
find('.js-build-output').text
......
......@@ -92,7 +92,9 @@ module QA
Page::Project::Pipeline::Show.act { go_to_first_job }
Page::Project::Job::Show.perform do |job|
job.wait(reload: false) { job.completed? }
job.wait(reload: false) do
job.completed? && !job.trace_loading?
end
expect(job.passed?).to be_truthy, "Job status did not become \"passed\"."
expect(job.output).to include(sha1sum)
......
......@@ -32,6 +32,8 @@ module Support
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:key?).and_call_original
allow(ENV).to receive(:fetch).and_call_original
# Prevent secrets from leaking in CI
allow(ENV).to receive(:inspect).and_return([])
add_stubbed_value(STUBBED_KEY, true)
end
end
......
......@@ -18,7 +18,7 @@ describe Boards::IssuesController do
end
describe 'GET index', :request_store do
let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join('spec/fixtures/dk.png'))) }
context 'with invalid board id' do
it 'returns a not found 404 response' do
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe Groups::AvatarsController do
let(:user) { create(:user) }
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
before do
group.add_owner(user)
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe Import::GitlabProjectsController do
set(:namespace) { create(:namespace) }
set(:user) { namespace.owner }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
let(:file) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
before do
sign_in(user)
......
......@@ -4,7 +4,7 @@ describe Import::GoogleCodeController do
include ImportSpecHelper
let(:user) { create(:user) }
let(:dump_file) { fixture_file_upload(Rails.root + 'spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
let(:dump_file) { fixture_file_upload('spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
before do
sign_in(user)
......
require 'spec_helper'
describe Profiles::AvatarsController do
let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
let(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
before do
sign_in(user)
......
require 'spec_helper'
describe Projects::AvatarsController do
let(:project) { create(:project, :repository, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
before do
......
......@@ -6,8 +6,8 @@ describe ProjectsController do
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
describe 'GET new' do
context 'with an authenticated user' do
......
......@@ -6,13 +6,13 @@ shared_examples 'content not cached without revalidation' do
end
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
describe 'POST create' do
let(:model) { 'personal_snippet' }
let(:snippet) { create(:personal_snippet, :public) }
let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
context 'when a user does not have permissions to upload a file' do
it "returns 401 when the user is not logged in" do
......@@ -205,7 +205,7 @@ describe UploadsController do
end
context "when viewing a project avatar" do
let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let!(:project) { create(:project, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when the project is public" do
before do
......@@ -314,7 +314,7 @@ describe UploadsController do
end
context "when viewing a group avatar" do
let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let!(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when the group is public" do
context "when not signed in" do
......@@ -521,7 +521,7 @@ describe UploadsController do
context 'Appearance' do
context 'when viewing a custom header logo' do
let!(:appearance) { create :appearance, header_logo: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
let!(:appearance) { create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
context 'when not signed in' do
it 'responds with status 200' do
......@@ -541,7 +541,7 @@ describe UploadsController do
end
context 'when viewing a custom logo' do
let!(:appearance) { create :appearance, logo: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
let!(:appearance) { create :appearance, logo: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
context 'when not signed in' do
it 'responds with status 200' do
......@@ -562,7 +562,7 @@ describe UploadsController do
end
context 'original filename or a version filename must match' do
let!(:appearance) { create :appearance, favicon: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
let!(:appearance) { create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
context 'has a valid filename on the original file' do
it 'successfully returns the file' do
......
......@@ -7,7 +7,7 @@ FactoryBot.define do
end
trait :with_file do
file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
file { fixture_file_upload("spec/fixtures/dk.png", "`/png") }
end
# The uniqueness constraint means we can't use the correct OID for all LFS
......
......@@ -130,11 +130,11 @@ FactoryBot.define do
end
trait :with_attachment do
attachment { fixture_file_upload(Rails.root.join( "spec/fixtures/dk.png"), "image/png") }
attachment { fixture_file_upload("spec/fixtures/dk.png", "image/png") }
end
trait :with_svg_attachment do
attachment { fixture_file_upload(Rails.root.join("spec/fixtures/unsanitized.svg"), "image/svg+xml") }
attachment { fixture_file_upload("spec/fixtures/unsanitized.svg", "image/svg+xml") }
end
transient do
......
......@@ -47,7 +47,7 @@ describe 'Commits' do
context 'commit status is Ci Build' do
let!(:build) { create(:ci_build, pipeline: pipeline) }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:artifacts_file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
before do
......
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.
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