summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <ClemMakesApps@gmail.com>2018-03-14 18:30:21 -0500
committerClement Ho <ClemMakesApps@gmail.com>2018-03-14 18:30:21 -0500
commit7c05073dd9d7d671e01cd428839772952ed2282a (patch)
tree7fb11d0702f658ec66040ad9b77371b68bc72e36
parentabefbcbeb0e4d7a1456eae14a339b35894714b7b (diff)
parent842f42f4b2de04e2257d0486d33162319297825e (diff)
downloadgitlab-ce-7c05073dd9d7d671e01cd428839772952ed2282a.tar.gz
Merge branch 'master' into csslab
-rw-r--r--.eslintrc27
-rw-r--r--.gitignore1
-rw-r--r--.prettierrc4
-rw-r--r--Gemfile18
-rw-r--r--Gemfile.lock63
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js3
-rw-r--r--app/assets/javascripts/pages/ci/lints/new/index.js (renamed from app/assets/javascripts/pages/ci/lints/create/index.js)0
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue144
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js113
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js16
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss35
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb51
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/views/admin/runners/show.html.haml4
-rw-r--r--changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml5
-rw-r--r--changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml5
-rw-r--r--changelogs/unreleased/44160-update-foreman-to-0-84-0.yml5
-rw-r--r--changelogs/unreleased/bvl-import-zip-multiple-assignees.yml5
-rw-r--r--changelogs/unreleased/fix-emoji-popup.yml5
-rw-r--r--changelogs/unreleased/mk-fix-move-upload-files-on-group-transfer.yml5
-rw-r--r--config/application.rb2
-rw-r--r--doc/administration/incoming_email.md1
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/development/new_fe_guide/style/index.md6
-rw-r--r--doc/development/new_fe_guide/style/prettier.md45
-rw-r--r--features/project/graph.feature33
-rw-r--r--features/project/redirects.feature38
-rw-r--r--features/steps/project/graph.rb50
-rw-r--r--features/steps/project/redirects.rb67
-rw-r--r--lib/gitlab/import_export/relation_factory.rb9
-rw-r--r--lib/gitlab/project_transfer.rb16
-rw-r--r--lib/gitlab/shell.rb13
-rw-r--r--lib/tasks/gitlab/shell.rake2
-rw-r--r--package.json6
-rw-r--r--scripts/frontend/frontend_script_utils.js30
-rw-r--r--scripts/frontend/prettier.js103
-rw-r--r--spec/features/ci_lint_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/features/projects/graph_spec.rb75
-rw-r--r--spec/features/projects/redirects_spec.rb74
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js172
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js179
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js75
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.json31
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb12
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb59
-rw-r--r--spec/lib/gitlab/shell_spec.rb16
-rw-r--r--spec/models/ci/build_spec.rb123
-rw-r--r--spec/models/ci/pipeline_spec.rb6
-rw-r--r--spec/models/namespace_spec.rb57
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--yarn.lock6
63 files changed, 1134 insertions, 725 deletions
diff --git a/.eslintrc b/.eslintrc
index a86f8a841c3..3f187db0c07 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -35,15 +35,22 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
- "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
- "vue/html-self-closing": ["error", {
- "html": {
- "void": "always",
- "normal": "never",
- "component": "always"
- },
- "svg": "always",
- "math": "always"
- }]
+ "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
+ "no-mixed-operators": 0,
+ "space-before-function-paren": 0,
+ "curly": 0,
+ "arrow-parens": 0,
+ "vue/html-self-closing": [
+ "error",
+ {
+ "html": {
+ "void": "always",
+ "normal": "never",
+ "component": "always"
+ },
+ "svg": "always",
+ "math": "always"
+ }
+ ]
}
}
diff --git a/.gitignore b/.gitignore
index 305a7696bf1..ff1f23f4b20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
eslint-report.html
/.gitlab_shell_secret
.idea
+/.vscode/*
/.rbenv-version
.rbx/
/.ruby-gemset
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000000..a20502b7f06
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/Gemfile b/Gemfile
index 2793463fd81..6d3999e3243 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
-gem 'omniauth-saml', '~> 1.7.0'
+gem 'omniauth-saml', '~> 1.10.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
@@ -104,7 +104,7 @@ gem 'carrierwave', '~> 1.2'
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
-gem 'fog-aws', '~> 1.4'
+gem 'fog-aws', '~> 2.0'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
@@ -152,7 +152,7 @@ end
gem 'state_machines-activerecord', '~> 0.4.0'
# Issue tags
-gem 'acts-as-taggable-on', '~> 4.0'
+gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
gem 'sidekiq', '~> 5.0'
@@ -164,7 +164,7 @@ gem 'sidekiq-limit_fetch', '~> 3.4', require: false
gem 'rufus-scheduler', '~> 3.4'
# HTTP requests
-gem 'httparty', '~> 0.13.3'
+gem 'httparty', '~> 0.15.6'
# Colored output to console
gem 'rainbow', '~> 2.2'
@@ -264,7 +264,7 @@ gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.5.3'
+gem 'sentry-raven', '~> 2.7'
gem 'premailer-rails', '~> 1.9.7'
@@ -300,7 +300,7 @@ group :metrics do
end
group :development do
- gem 'foreman', '~> 0.78.0'
+ gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
@@ -381,14 +381,14 @@ group :test do
gem 'test-prof', '~> 0.2.5'
end
-gem 'octokit', '~> 4.6.2'
+gem 'octokit', '~> 4.8'
gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
-gem 'ruby-prof', '~> 0.16.2'
+gem 'ruby-prof', '~> 0.17.0'
# OAuth
gem 'oauth2', '~> 1.4'
@@ -401,7 +401,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
-gem 'net-ssh', '~> 4.1.0'
+gem 'net-ssh', '~> 4.2.0'
gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support
diff --git a/Gemfile.lock b/Gemfile.lock
index b85c7085d07..dbaf6a93ff8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -40,8 +40,8 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
- acts-as-taggable-on (4.0.0)
- activerecord (>= 4.0)
+ acts-as-taggable-on (5.0.0)
+ activerecord (>= 4.2.8)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
@@ -194,7 +194,7 @@ GEM
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
- excon (0.57.1)
+ excon (0.60.0)
execjs (2.6.0)
expression_parser (0.9.0)
factory_bot (4.8.2)
@@ -233,14 +233,14 @@ GEM
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (1.4.0)
+ fog-aws (2.0.1)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-core (1.44.3)
+ fog-core (1.45.0)
builder
- excon (~> 0.49)
+ excon (~> 0.58)
formatador (~> 0.2)
fog-google (0.5.3)
fog-core
@@ -265,7 +265,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
- foreman (0.78.0)
+ foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
fuubar (2.2.0)
@@ -388,7 +388,7 @@ GEM
thor
tilt
hashdiff (0.3.4)
- hashie (3.5.6)
+ hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
@@ -411,11 +411,10 @@ GEM
domain_name (~> 0.5)
http-form_data (1.0.1)
http_parser.rb (0.6.0)
- httparty (0.13.7)
- json (~> 1.8)
+ httparty (0.15.7)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
- i18n (0.9.1)
+ i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.2.3)
@@ -513,7 +512,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.0)
- net-ssh (4.1.0)
+ net-ssh (4.2.0)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
@@ -525,12 +524,12 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
- octokit (4.6.2)
+ octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5)
- omniauth (1.4.2)
+ omniauth (1.4.3)
hashie (>= 1.2, < 4)
- rack (>= 1.0, < 3)
+ rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1)
@@ -569,9 +568,9 @@ GEM
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
- omniauth-saml (1.7.0)
- omniauth (~> 1.3)
- ruby-saml (~> 1.4)
+ omniauth-saml (1.10.0)
+ omniauth (~> 1.3, >= 1.3.2)
+ ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
@@ -650,7 +649,7 @@ GEM
pry (>= 0.9.10)
public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3)
- rack (1.6.8)
+ rack (1.6.9)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
@@ -803,9 +802,9 @@ GEM
i18n
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-prof (0.16.2)
+ ruby-prof (0.17.0)
ruby-progressbar (1.9.0)
- ruby-saml (1.4.1)
+ ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
@@ -844,7 +843,7 @@ GEM
selenium-webdriver (3.5.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
- sentry-raven (2.5.3)
+ sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
@@ -933,7 +932,7 @@ GEM
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0)
- tzinfo (1.2.4)
+ tzinfo (1.2.5)
thread_safe (~> 0.1)
u2f (0.2.1)
uber (0.1.0)
@@ -994,7 +993,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2)
- acts-as-taggable-on (~> 4.0)
+ acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
allocations (~> 1.0)
@@ -1048,14 +1047,14 @@ DEPENDENCIES
flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0)
fog-aliyun (~> 0.2.0)
- fog-aws (~> 1.4)
+ fog-aws (~> 2.0)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
- foreman (~> 0.78.0)
+ foreman (~> 0.84.0)
fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.3)
@@ -1086,7 +1085,7 @@ DEPENDENCIES
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
html2text
- httparty (~> 0.13.3)
+ httparty (~> 0.15.6)
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
@@ -1107,10 +1106,10 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
- net-ssh (~> 4.1.0)
+ net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
- octokit (~> 4.6.2)
+ octokit (~> 4.8)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
@@ -1123,7 +1122,7 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
- omniauth-saml (~> 1.7.0)
+ omniauth-saml (~> 1.10.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
@@ -1173,7 +1172,7 @@ DEPENDENCIES
rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
- ruby-prof (~> 0.16.2)
+ ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.26.0)
@@ -1183,7 +1182,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
- sentry-raven (~> 2.5.3)
+ sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 43a5325cf71..8259133c95b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -132,9 +132,8 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
matcher(flag, subtext) {
- const relevantText = subtext.trim().split(/\s/).pop();
const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
- const match = regexp.exec(relevantText);
+ const match = regexp.exec(subtext);
return match && match.length ? match[1] : null;
},
diff --git a/app/assets/javascripts/pages/ci/lints/create/index.js b/app/assets/javascripts/pages/ci/lints/new/index.js
index 8e8a843da0b..8e8a843da0b 100644
--- a/app/assets/javascripts/pages/ci/lints/create/index.js
+++ b/app/assets/javascripts/pages/ci/lints/new/index.js
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
new file mode 100644
index 00000000000..7bef2e97349
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -0,0 +1,144 @@
+<script>
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import tooltip from '../../vue_shared/directives/tooltip';
+import LoadingButton from '../../vue_shared/components/loading_button.vue';
+import { visitUrl } from '../../lib/utils/url_utility';
+import createFlash from '../../flash';
+import MemoryUsage from './memory_usage.vue';
+import StatusIcon from './mr_widget_status_icon.vue';
+import MRWidgetService from '../services/mr_widget_service';
+
+export default {
+ name: 'Deployment',
+ components: {
+ LoadingButton,
+ MemoryUsage,
+ StatusIcon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
+ props: {
+ deployment: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isStopping: false,
+ };
+ },
+ computed: {
+ deployTimeago() {
+ return this.timeFormated(this.deployment.deployed_at);
+ },
+ hasExternalUrls() {
+ return !!(this.deployment.external_url && this.deployment.external_url_formatted);
+ },
+ hasDeploymentTime() {
+ return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
+ },
+ hasDeploymentMeta() {
+ return !!(this.deployment.url && this.deployment.name);
+ },
+ hasMetrics() {
+ return !!(this.deployment.metrics_url);
+ },
+ },
+ methods: {
+ stopEnvironment() {
+ const msg = 'Are you sure you want to stop this environment?';
+ const isConfirmed = confirm(msg); // eslint-disable-line
+
+ if (isConfirmed) {
+ this.isStopping = true;
+
+ MRWidgetService.stopEnvironment(this.deployment.stop_url)
+ .then(res => res.data)
+ .then((data) => {
+ if (data.redirect_url) {
+ visitUrl(data.redirect_url);
+ }
+
+ this.isStopping = false;
+ })
+ .catch(() => {
+ createFlash('Something went wrong while stopping this environment. Please try again.');
+ this.isStopping = false;
+ });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="mr-widget-heading deploy-heading">
+ <div class="ci-widget media">
+ <div class="ci-status-icon ci-status-icon-success">
+ <span class="js-icon-link icon-link">
+ <status-icon status="success" />
+ </span>
+ </div>
+ <div class="media-body">
+ <div class="deploy-body">
+ <template v-if="hasDeploymentMeta">
+ <span>
+ Deployed to
+ </span>
+ <a
+ :href="deployment.url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-meta"
+ >
+ {{ deployment.name }}
+ </a>
+ </template>
+ <template v-if="hasExternalUrls">
+ <span>
+ on
+ </span>
+ <a
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-url"
+ >
+ <i
+ class="fa fa-external-link"
+ aria-hidden="true"
+ >
+ </i>
+ {{ deployment.external_url_formatted }}
+ </a>
+ </template>
+ <span
+ v-if="hasDeploymentTime"
+ v-tooltip
+ :title="deployment.deployed_at_formatted"
+ class="js-deploy-time"
+ >
+ {{ deployTimeago }}
+ </span>
+ <loading-button
+ v-if="deployment.stop_url"
+ container-class="btn btn-default btn-xs prepend-left-default"
+ label="Stop environment"
+ :loading="isStopping"
+ @click="stopEnvironment"
+ />
+ </div>
+ <memory-usage
+ v-if="hasMetrics"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
deleted file mode 100644
index c7f992384c8..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { getTimeago } from '~/lib/utils/datetime_utility';
-import { visitUrl } from '../../lib/utils/url_utility';
-import Flash from '../../flash';
-import MemoryUsage from './memory_usage.vue';
-import StatusIcon from './mr_widget_status_icon.vue';
-import MRWidgetService from '../services/mr_widget_service';
-
-export default {
- name: 'MRWidgetDeployment',
- props: {
- mr: { type: Object, required: true },
- service: { type: Object, required: true },
- },
- components: {
- MemoryUsage,
- StatusIcon,
- },
- methods: {
- formatDate(date) {
- return getTimeago().format(date);
- },
- hasExternalUrls(deployment = {}) {
- return deployment.external_url && deployment.external_url_formatted;
- },
- hasDeploymentTime(deployment = {}) {
- return deployment.deployed_at && deployment.deployed_at_formatted;
- },
- hasDeploymentMeta(deployment = {}) {
- return deployment.url && deployment.name;
- },
- stopEnvironment(deployment) {
- const msg = 'Are you sure you want to stop this environment?';
- const isConfirmed = confirm(msg); // eslint-disable-line
-
- if (isConfirmed) {
- MRWidgetService.stopEnvironment(deployment.stop_url)
- .then(res => res.data)
- .then((data) => {
- if (data.redirect_url) {
- visitUrl(data.redirect_url);
- }
- })
- .catch(() => {
- new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line
- });
- }
- },
- },
- template: `
- <div class="mr-widget-heading deploy-heading">
- <div v-for="deployment in mr.deployments">
- <div class="ci-widget media">
- <div class="ci-status-icon ci-status-icon-success">
- <span class="js-icon-link icon-link">
- <status-icon status="success" />
- </span>
- </div>
- <div class="media-body space-children">
- <span>
- <span
- v-if="hasDeploymentMeta(deployment)">
- Deployed to
- </span>
- <a
- v-if="hasDeploymentMeta(deployment)"
- :href="deployment.url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-meta inline">
- {{deployment.name}}
- </a>
- <span
- v-if="hasExternalUrls(deployment)">
- on
- </span>
- <a
- v-if="hasExternalUrls(deployment)"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-url inline">
- <i
- class="fa fa-external-link"
- aria-hidden="true" />
- {{deployment.external_url_formatted}}
- </a>
- <span
- v-if="hasDeploymentTime(deployment)"
- :data-title="deployment.deployed_at_formatted"
- class="js-deploy-time"
- data-toggle="tooltip"
- data-placement="top">
- {{formatDate(deployment.deployed_at)}}
- </span>
- </span>
- <button
- type="button"
- v-if="deployment.stop_url"
- @click="stopEnvironment(deployment)"
- class="btn btn-default btn-xs">
- Stop environment
- </button>
- <memory-usage
- v-if="deployment.metrics_url"
- :metrics-url="deployment.metrics_url"
- :metrics-monitoring-url="deployment.metrics_monitoring_url"
- />
- </div>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 20624aad0ad..efbe1c96d1c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval';
export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
-export { default as WidgetDeployment } from './components/mr_widget_deployment';
+export { default as Deployment } from './components/deployment.vue';
export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index cc8bc6af1e1..169adfe0a1d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -5,7 +5,7 @@ import {
WidgetHeader,
WidgetMergeHelp,
WidgetPipeline,
- WidgetDeployment,
+ Deployment,
WidgetMaintainerEdit,
WidgetRelatedLinks,
MergedState,
@@ -67,9 +67,6 @@ export default {
shouldRenderRelatedLinks() {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
- shouldRenderDeployments() {
- return this.mr.deployments.length;
- },
shouldRenderSourceBranchRemovalStatus() {
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
(!this.mr.isNothingToMergeState && !this.mr.isMergedState);
@@ -216,7 +213,7 @@ export default {
'mr-widget-header': WidgetHeader,
'mr-widget-merge-help': WidgetMergeHelp,
'mr-widget-pipeline': WidgetPipeline,
- 'mr-widget-deployment': WidgetDeployment,
+ Deployment,
'mr-widget-maintainer-edit': WidgetMaintainerEdit,
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
@@ -250,10 +247,11 @@ export default {
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
/>
- <mr-widget-deployment
- v-if="shouldRenderDeployments"
- :mr="mr"
- :service="service" />
+ <deployment
+ v-for="deployment in mr.deployments"
+ :key="deployment.id"
+ :deployment="deployment"
+ />
<div class="mr-widget-section">
<component
:is="componentName"
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f887a11004f..4692d0fb873 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -718,6 +718,8 @@
}
.mr-memory-usage {
+ width: 100%;
+
p.usage-info-loading .usage-info-load-spinner {
margin-right: 10px;
font-size: 16px;
@@ -727,3 +729,36 @@
.fork-sprite {
margin-right: -5px;
}
+
+.deploy-heading {
+ .media-body {
+ min-width: 0;
+ }
+}
+
+.deploy-body {
+ display: flex;
+ flex-wrap: wrap;
+
+ @media (min-width: $screen-xs) {
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ }
+
+ > *:not(:last-child) {
+ margin-right: .3em;
+ }
+}
+
+.deploy-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 100px;
+ max-width: 150px;
+
+ @media (min-width: $screen-xs) {
+ min-width: 0;
+ max-width: 100%;
+ }
+}
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 67a988addbe..f05e606995d 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -7,29 +7,24 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
- expires_full_path_cache
-
- # Move the namespace directory in all storage paths used by member projects
- repository_storage_paths.each do |repository_storage_path|
- # Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage_path, full_path_was)
-
- # Ensure new directory exists before moving it (if there's a parent)
- gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+ parent_was = if parent_changed? && parent_id_was.present?
+ Namespace.find(parent_id_was) # raise NotFound early if needed
+ end
- unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+ expires_full_path_cache
- Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+ move_repositories
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
- end
+ if parent_changed?
+ former_parent_full_path = parent_was&.full_path
+ parent_full_path = parent&.full_path
+ Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+ Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+ else
+ Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
+ Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
end
- Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
- Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
-
remove_exports!
# If repositories moved successfully we need to
@@ -57,6 +52,26 @@ module Storage
private
+ def move_repositories
+ # Move the namespace directory in all storage paths used by member projects
+ repository_storage_paths.each do |repository_storage_path|
+ # Ensure old directory exists before moving it
+ gitlab_shell.add_namespace(repository_storage_path, full_path_was)
+
+ # Ensure new directory exists before moving it (if there's a parent)
+ gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+
+ unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+
+ Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ end
+ end
+ end
+
def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 36090676051..ec8156bbb01 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -55,7 +55,7 @@ class Member < ActiveRecord::Base
scope :active_without_invites, -> do
left_join_users
.where(users: { state: 'active' })
- .where(requested_at: nil)
+ .non_request
.reorder(nil)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 5f9d9785d64..0183e3d0a38 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1083,7 +1083,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.add_repository(repository_storage, disk_path)
+ if gitlab_shell.create_repository(repository_storage, disk_path)
repository.after_create
true
else
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f6041da986c..52e067cb44c 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -169,7 +169,7 @@ class ProjectWiki
private
def create_repo!(raw_repository)
- gitlab_shell.add_repository(project.repository_storage, disk_path)
+ gitlab_shell.create_repository(project.repository_storage, disk_path)
raise CouldNotCreateWikiError unless raw_repository.exists?
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e07ecda27b5..ab94db2c1e5 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -208,9 +208,9 @@ class NotificationService
def new_access_request(member)
return true unless member.notifiable?(:subscription)
- recipients = member.source.members.owners_and_masters
+ recipients = member.source.members.active_without_invites.owners_and_masters
if fallback_to_group_owners_masters?(recipients, member)
- recipients = member.source.group.members.owners_and_masters
+ recipients = member.source.group.members.active_without_invites.owners_and_masters
end
recipients.each { |recipient| deliver_access_request_email(recipient, member) }
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 185e9d7b35d..37269862de6 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -9,6 +9,10 @@
%span.runner-state.runner-state-specific
Specific
+- add_to_breadcrumbs _("Runners"), admin_runners_path
+- breadcrumb_title "##{@runner.id}"
+- @no_container = true
+
- if @runner.shared?
.bs-callout.bs-callout-success
%h4 This Runner will process jobs from ALL UNASSIGNED projects
diff --git a/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
new file mode 100644
index 00000000000..3aec71d5ac4
--- /dev/null
+++ b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
@@ -0,0 +1,5 @@
+---
+title: Set breadcrumb for admin/runners/show
+merge_request: 17431
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
new file mode 100644
index 00000000000..7335d313510
--- /dev/null
+++ b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
+merge_request: 17734
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
new file mode 100644
index 00000000000..990d188eb78
--- /dev/null
+++ b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update foreman from 0.78.0 to 0.84.0
+merge_request: 17690
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
new file mode 100644
index 00000000000..86bd5faf8ed
--- /dev/null
+++ b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
@@ -0,0 +1,5 @@
+---
+title: Fix importing multiple assignees from GitLab export
+merge_request: 17718
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-emoji-popup.yml b/changelogs/unreleased/fix-emoji-popup.yml
new file mode 100644
index 00000000000..c81d061a5d7
--- /dev/null
+++ b/changelogs/unreleased/fix-emoji-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Hide emoji popup after multiple spaces
+merge_request:
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/mk-fix-move-upload-files-on-group-transfer.yml b/changelogs/unreleased/mk-fix-move-upload-files-on-group-transfer.yml
new file mode 100644
index 00000000000..ba366b81600
--- /dev/null
+++ b/changelogs/unreleased/mk-fix-move-upload-files-on-group-transfer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing uploads after group transfer
+merge_request: 17658
+author:
+type: fixed
diff --git a/config/application.rb b/config/application.rb
index 294cd4c3090..93a0f780059 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -70,7 +70,6 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
- # - Secret variable values (:value)
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
@@ -82,7 +81,6 @@ module Gitlab
sentry_dsn
trace
variables
- value
)
# Enable escaping HTML in JSON.
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 6c5a466ced5..27a3710632d 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -187,6 +187,7 @@ for a real-world example of this exploit.
```sh
sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
```
1. Verify that everything is configured correctly:
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index b732cc65b73..960eabd5538 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -17,10 +17,13 @@ are very appreciative of the work done by translators and proofreaders!
- French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German
+- Indonesian
+ - Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
- Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
- Japanese
- Korean
+ - Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Polish
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
diff --git a/doc/development/new_fe_guide/style/index.md b/doc/development/new_fe_guide/style/index.md
index d2d576b3b46..ebee57bebbf 100644
--- a/doc/development/new_fe_guide/style/index.md
+++ b/doc/development/new_fe_guide/style/index.md
@@ -7,3 +7,9 @@
## [JavaScript style guide](javascript.md)
## [Vue style guide](vue.md)
+
+# Tooling
+
+## [Prettier](prettier.md)
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our guidelines.
diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md
new file mode 100644
index 00000000000..eb18189282b
--- /dev/null
+++ b/doc/development/new_fe_guide/style/prettier.md
@@ -0,0 +1,45 @@
+# Formatting with Prettier
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting .js, .vue, and .scss files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
+
+## Editor
+
+The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
+
+Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
+
+```
+ "prettier.disableLanguages": [
+ "json",
+ "markdown"
+ ],
+```
+
+## Yarn Script
+
+The following yarn scripts are available to do global formatting:
+
+```
+yarn prettier-staged-save
+```
+
+Updates all currently staged files (based on `git diff`) with Prettier and saves the needed changes.
+
+```
+yarn prettier-staged
+```
+Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
+
+```
+yarn prettier-all
+```
+
+Checks all files with Prettier and logs which files need manual updating to the console.
+
+```
+yarn prettier-all-save
+```
+
+Formats all files in the repository with Prettier. (This should only be used to test global rule updates otherwise you would end up with huge MR's).
+
+The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js`.
diff --git a/features/project/graph.feature b/features/project/graph.feature
deleted file mode 100644
index b25c73ad870..00000000000
--- a/features/project/graph.feature
+++ /dev/null
@@ -1,33 +0,0 @@
-Feature: Project Graph
- Background:
- Given I sign in as a user
- And I own project "Shop"
-
- @javascript
- Scenario: I should see project graphs
- When I visit project "Shop" graph page
- Then page should have graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on commits graph url
- When I visit project "Shop" commits graph page
- Then page should have commits graphs
- Then page should have languages graphs
-
- @javascript
- Scenario: I should see project ci graphs
- Given project "Shop" has CI enabled
- When I visit project "Shop" CI graph page
- Then page should have CI graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on language graph url
- When I visit project "Shop" languages graph page
- Then page should have languages graphs
- Then page should have commits graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on charts url
- When I visit project "Shop" chart page
- Then page should have languages graphs
- Then page should have commits graphs
diff --git a/features/project/redirects.feature b/features/project/redirects.feature
deleted file mode 100644
index a2e77e7bf30..00000000000
--- a/features/project/redirects.feature
+++ /dev/null
@@ -1,38 +0,0 @@
-Feature: Project Redirects
- Background:
- Given public project "Community"
- And private project "Enterprise"
-
- Scenario: I visit public project page
- When I visit project "Community" page
- Then I should see project "Community" home page
-
- Scenario: I visit private project page
- When I visit project "Enterprise" page
- Then I should be redirected to sign in page
-
- Scenario: I visit a non-existent project page
- When I visit project "CommunityDoesNotExist" page
- Then I should be redirected to sign in page
-
- Scenario: I visit a non-existent project page as user
- Given I sign in as a user
- When I visit project "CommunityDoesNotExist" page
- Then page status code should be 404
-
- Scenario: I visit unauthorized project page as user
- Given I sign in as a user
- When I visit project "Enterprise" page
- Then page status code should be 404
-
- Scenario: I visit a public project without signing in
- When I visit project "Community" page
- And I should see project "Community" home page
- And I click on "Sign In"
- And Authenticate
- Then I should be redirected to "Community" page
-
- Scenario: I visit private project page without signing in
- When I visit project "Enterprise" page
- And I get redirected to signin page where I sign in
- Then I should be redirected to "Enterprise" page
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
deleted file mode 100644
index b9cddf4041d..00000000000
--- a/features/steps/project/graph.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
-
- step 'page should have graphs' do
- expect(page).to have_selector ".stat-graph"
- end
-
- When 'I visit project "Shop" graph page' do
- visit project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" commits graph page' do
- visit commits_project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" languages graph page' do
- visit languages_project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" chart page' do
- visit charts_project_graph_path(project, "master")
- end
-
- step 'page should have languages graphs' do
- expect(page).to have_content /Ruby 66.* %/
- expect(page).to have_content /JavaScript 22.* %/
- end
-
- step 'page should have commits graphs' do
- expect(page).to have_content "Commit statistics for master"
- expect(page).to have_content "Commits per day of month"
- end
-
- step 'I visit project "Shop" CI graph page' do
- visit ci_project_graph_path(project, 'master')
- end
-
- step 'page should have CI graphs' do
- expect(page).to have_content 'Overall'
- expect(page).to have_content 'Pipelines for last week'
- expect(page).to have_content 'Pipelines for last month'
- expect(page).to have_content 'Pipelines for last year'
- expect(page).to have_content 'Commit duration in minutes for last 30 commits'
- end
-
- def project
- @project ||= Project.find_by(name: "Shop")
- end
-end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
deleted file mode 100644
index 9ce86ca45d0..00000000000
--- a/features/steps/project/redirects.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'public project "Community"' do
- create(:project, :public, name: 'Community')
- end
-
- step 'private project "Enterprise"' do
- create(:project, :private, name: 'Enterprise')
- end
-
- step 'I visit project "Community" page' do
- project = Project.find_by(name: 'Community')
- visit project_path(project)
- end
-
- step 'I should see project "Community" home page' do
- Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
- page.within '.breadcrumbs .breadcrumb-item-text' do
- expect(page).to have_content 'Community'
- end
- end
-
- step 'I visit project "Enterprise" page' do
- project = Project.find_by(name: 'Enterprise')
- visit project_path(project)
- end
-
- step 'I visit project "CommunityDoesNotExist" page' do
- project = Project.find_by(name: 'Community')
- visit project_path(project) + 'DoesNotExist'
- end
-
- step 'I click on "Sign In"' do
- first(:link, "Sign in").click
- end
-
- step 'Authenticate' do
- admin = create(:admin)
- fill_in "user_login", with: admin.email
- fill_in "user_password", with: admin.password
- click_button "Sign in"
- Thread.current[:current_user] = admin
- end
-
- step 'I should be redirected to "Community" page' do
- project = Project.find_by(name: 'Community')
- expect(current_path).to eq "/#{project.full_path}"
- expect(status_code).to eq 200
- end
-
- step 'I get redirected to signin page where I sign in' do
- admin = create(:admin)
- fill_in "user_login", with: admin.email
- fill_in "user_password", with: admin.password
- click_button "Sign in"
- Thread.current[:current_user] = admin
- end
-
- step 'I should be redirected to "Enterprise" page' do
- project = Project.find_by(name: 'Enterprise')
- expect(current_path).to eq "/#{project.full_path}"
- expect(status_code).to eq 200
- end
-end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index cf6b7e306dd..791a54e1b69 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -70,6 +70,7 @@ module Gitlab
update_user_references
update_project_references
+ remove_duplicate_assignees
reset_tokens!
remove_encrypted_attributes!
@@ -83,6 +84,14 @@ module Gitlab
end
end
+ def remove_duplicate_assignees
+ return unless @relation_hash['issue_assignees']
+
+ # When an assignee did not exist in the members mapper, the importer is
+ # assigned. We only need to assign each user once.
+ @relation_hash['issue_assignees'].uniq!(&:user_id)
+ end
+
def setup_note
set_note_author
# attachment is deprecated and note uploads are handled by Markdown uploader
diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb
index 1bba0b78e2f..690c38737c0 100644
--- a/lib/gitlab/project_transfer.rb
+++ b/lib/gitlab/project_transfer.rb
@@ -1,13 +1,19 @@
module Gitlab
+ # This class is used to move local, unhashed files owned by projects to their new location
class ProjectTransfer
- def move_project(project_path, namespace_path_was, namespace_path)
- new_namespace_folder = File.join(root_dir, namespace_path)
- FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
- from = File.join(root_dir, namespace_path_was, project_path)
- to = File.join(root_dir, namespace_path, project_path)
+ # nil parent_path (or parent_path_was) represents a root namespace
+ def move_namespace(path, parent_path_was, parent_path)
+ parent_path_was ||= ''
+ parent_path ||= ''
+ new_parent_folder = File.join(root_dir, parent_path)
+ FileUtils.mkdir_p(new_parent_folder)
+ from = File.join(root_dir, parent_path_was, path)
+ to = File.join(root_dir, parent_path, path)
move(from, to, "")
end
+ alias_method :move_project, :move_namespace
+
def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir)
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index dda7afc0999..3a8f5826818 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -69,13 +69,14 @@ module Gitlab
# name - project disk path
#
# Ex.
- # add_repository("/path/to/storage", "gitlab/gitlab-ci")
+ # create_repository("/path/to/storage", "gitlab/gitlab-ci")
#
- def add_repository(storage, name)
+ def create_repository(storage, name)
relative_path = name.dup
relative_path << '.git' unless relative_path.end_with?('.git')
- gitaly_migrate(:create_repository) do |is_enabled|
+ gitaly_migrate(:create_repository,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
repository = Gitlab::Git::Repository.new(storage, relative_path, '')
repository.gitaly_repository_client.create_repository
@@ -85,7 +86,7 @@ module Gitlab
Gitlab::Git::Repository.create(repo_path, bare: true, symlink_hooks_to: gitlab_shell_hooks_path)
end
end
- rescue => err
+ rescue => err # Once the Rugged codes gets removes this can be improved
Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
false
end
@@ -487,8 +488,8 @@ module Gitlab
Gitlab.config.gitlab_shell.git_timeout
end
- def gitaly_migrate(method, &block)
- Gitlab::GitalyClient.migrate(method, &block)
+ def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
+ Gitlab::GitalyClient.migrate(method, status: status, &block)
rescue GRPC::NotFound, GRPC::BadStatus => e
# Old Popen code returns [Error, output] to the caller, so we
# need to do the same here...
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 844664b12d4..4fcbbbf8c9d 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -69,7 +69,7 @@ namespace :gitlab do
if File.exist?(path_to_repo)
print '-'
else
- if Gitlab::Shell.new.add_repository(project.repository_storage,
+ if Gitlab::Shell.new.create_repository(project.repository_storage,
project.disk_path)
print '.'
else
diff --git a/package.json b/package.json
index 2f5f44e039c..815870007a7 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,10 @@
"karma": "karma start config/karma.config.js --single-run",
"karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js",
+ "prettier-staged": "node ./scripts/frontend/prettier.js",
+ "prettier-staged-save": "node ./scripts/frontend/prettier.js save",
+ "prettier-all": "node ./scripts/frontend/prettier.js check-all",
+ "prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
@@ -115,7 +119,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.7",
"nodemon": "^1.15.1",
- "prettier": "1.9.2",
+ "prettier": "1.11.1",
"webpack-dev-server": "^2.11.2"
}
}
diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js
new file mode 100644
index 00000000000..2c06747255c
--- /dev/null
+++ b/scripts/frontend/frontend_script_utils.js
@@ -0,0 +1,30 @@
+/* eslint import/no-commonjs: "off" */
+const execFileSync = require('child_process').execFileSync;
+
+const exec = (command, args) => {
+ const options = {
+ cwd: process.cwd(),
+ env: process.env,
+ encoding: 'utf-8',
+ };
+ return execFileSync(command, args, options);
+};
+
+const execGitCmd = args =>
+ exec('git', args)
+ .trim()
+ .toString()
+ .split('\n');
+
+module.exports = {
+ getStagedFiles: fileExtensionFilter => {
+ const gitOptions = [
+ 'diff',
+ '--name-only',
+ '--cached',
+ '--diff-filter=ACMRTUB',
+ ];
+ if (fileExtensionFilter) gitOptions.push(...fileExtensionFilter);
+ return execGitCmd(gitOptions);
+ },
+};
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
new file mode 100644
index 00000000000..863572bf64d
--- /dev/null
+++ b/scripts/frontend/prettier.js
@@ -0,0 +1,103 @@
+/* eslint import/no-commonjs: "off", import/no-extraneous-dependencies: "off", no-console: "off" */
+const glob = require('glob');
+const prettier = require('prettier');
+const fs = require('fs');
+
+const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
+
+const mode = process.argv[2] || 'check';
+const shouldSave = mode === 'save' || mode === 'save-all';
+const allFiles = mode === 'check-all' || mode === 'save-all';
+
+const config = {
+ patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
+ ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
+ parsers: {
+ js: 'babylon',
+ vue: 'vue',
+ scss: 'css',
+ },
+};
+const availableExtensions = Object.keys(config.parsers);
+
+console.log(`Loading ${allFiles ? 'All' : 'Staged'} Files ...`);
+
+const stagedFiles = allFiles
+ ? null
+ : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
+
+if (stagedFiles) {
+ if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
+ console.log('No matching staged files.');
+ return;
+ }
+ console.log(`Matching staged Files : ${stagedFiles.length}`);
+}
+
+let didWarn = false;
+let didError = false;
+
+let files;
+if (allFiles) {
+ const ignore = config.ignore;
+ const patterns = config.patterns;
+ const globPattern =
+ patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
+ files = glob
+ .sync(globPattern, { ignore })
+ .filter(f => allFiles || stagedFiles.includes(f));
+} else {
+ files = stagedFiles.filter(f =>
+ availableExtensions.includes(f.split('.').pop()),
+ );
+}
+
+if (!files.length) {
+ console.log('No Files found to process with Prettier');
+ return;
+}
+
+console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
+
+prettier
+ .resolveConfig('.')
+ .then(options => {
+ console.log('Found options : ', options);
+ files.forEach(file => {
+ try {
+ const fileExtension = file.split('.').pop();
+ Object.assign(options, {
+ parser: config.parsers[fileExtension],
+ });
+
+ const input = fs.readFileSync(file, 'utf8');
+
+ if (shouldSave) {
+ const output = prettier.format(input, options);
+ if (output !== input) {
+ fs.writeFileSync(file, output, 'utf8');
+ console.log(`Prettified : ${file}`);
+ }
+ } else if (!prettier.check(input, options)) {
+ if (!didWarn) {
+ console.log(
+ '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n',
+ );
+ didWarn = true;
+ }
+ console.log(`Prettify Manually : ${file}`);
+ }
+ } catch (error) {
+ didError = true;
+ console.log(`\n\nError with ${file}: ${error.message}`);
+ }
+ });
+
+ if (didWarn || didError) {
+ process.exit(1);
+ }
+ })
+ .catch(e => {
+ console.log(`Error on loading the Config File: ${e.message}`);
+ process.exit(1);
+ });
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index b1dceec9da8..220b934154e 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -39,6 +39,7 @@ describe 'CI Lint', :js do
it 'displays information about an error' do
expect(page).to have_content('Status: syntax is incorrect')
+ expect(page).to have_selector('.ace_content', text: yaml_content)
end
end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 3abe363d523..f744d7941f5 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -22,7 +22,7 @@ describe 'Merge request > User sees deployment widget', :js do
wait_for_requests
expect(page).to have_content("Deployed to #{environment.name}")
- expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+ expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
end
context 'with stop action' do
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
new file mode 100644
index 00000000000..57172610aed
--- /dev/null
+++ b/spec/features/projects/graph_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe 'Project Graph', :js do
+ let(:user) { create :user }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+ before do
+ project.add_master(user)
+
+ sign_in(user)
+ end
+
+ shared_examples 'page should have commits graphs' do
+ it 'renders commits' do
+ expect(page).to have_content('Commit statistics for master')
+ expect(page).to have_content('Commits per day of month')
+ end
+ end
+
+ shared_examples 'page should have languages graphs' do
+ it 'renders languages' do
+ expect(page).to have_content(/Ruby 66.* %/)
+ expect(page).to have_content(/JavaScript 22.* %/)
+ end
+ end
+
+ it 'renders graphs' do
+ visit project_graph_path(project, 'master')
+
+ expect(page).to have_selector('.stat-graph', visible: false)
+ end
+
+ context 'commits graph' do
+ before do
+ visit commits_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'languages graph' do
+ before do
+ visit languages_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'charts graph' do
+ before do
+ visit charts_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'when CI enabled' do
+ before do
+ project.enable_ci
+
+ visit ci_project_graph_path(project, 'master')
+ end
+
+ it 'renders CI graphs' do
+ expect(page).to have_content 'Overall'
+ expect(page).to have_content 'Pipelines for last week'
+ expect(page).to have_content 'Pipelines for last month'
+ expect(page).to have_content 'Pipelines for last year'
+ expect(page).to have_content 'Commit duration in minutes for last 30 commits'
+ end
+ end
+end
diff --git a/spec/features/projects/redirects_spec.rb b/spec/features/projects/redirects_spec.rb
new file mode 100644
index 00000000000..d1d8ca07035
--- /dev/null
+++ b/spec/features/projects/redirects_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe 'Project redirects' do
+ let(:user) { create :user }
+ let(:public_project) { create :project, :public }
+ let(:private_project) { create :project, :private }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
+ end
+
+ it 'shows public project page' do
+ visit project_path(public_project)
+
+ page.within '.breadcrumbs .breadcrumb-item-text' do
+ expect(page).to have_content(public_project.name)
+ end
+ end
+
+ it 'redirects to sign in page when project is private' do
+ visit project_path(private_project)
+
+ expect(current_path).to eq(new_user_session_path)
+ end
+
+ it 'redirects to sign in page when project does not exist' do
+ visit project_path(build(:project, :public))
+
+ expect(current_path).to eq(new_user_session_path)
+ end
+
+ it 'redirects to public project page after signing in' do
+ visit project_path(public_project)
+
+ first(:link, 'Sign in').click
+
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: user.password
+ click_button 'Sign in'
+
+ expect(status_code).to eq(200)
+ expect(current_path).to eq("/#{public_project.full_path}")
+ end
+
+ it 'redirects to private project page after sign in' do
+ visit project_path(private_project)
+
+ owner = private_project.owner
+ fill_in 'user_login', with: owner.email
+ fill_in 'user_password', with: owner.password
+ click_button 'Sign in'
+
+ expect(status_code).to eq(200)
+ expect(current_path).to eq("/#{private_project.full_path}")
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'returns 404 status when project does not exist' do
+ visit project_path(build(:project, :public))
+
+ expect(status_code).to eq(404)
+ end
+
+ it 'returns 404 when project is private' do
+ visit project_path(private_project)
+
+ expect(status_code).to eq(404)
+ end
+ end
+end
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
new file mode 100644
index 00000000000..ff8d54c029f
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import * as urlUtils from '~/lib/utils/url_utility';
+import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+
+const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+};
+const createComponent = () => {
+ const Component = Vue.extend(deploymentComponent);
+
+ return new Component({
+ el: document.createElement('div'),
+ propsData: { deployment: { ...deploymentMockData } },
+ });
+};
+
+describe('Deployment component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('deployTimeago', () => {
+ it('return formatted date', () => {
+ const readable = getTimeago().format(deploymentMockData.deployed_at);
+ expect(vm.deployTimeago).toEqual(readable);
+ });
+ });
+
+ describe('hasExternalUrls', () => {
+ it('should return true', () => {
+ expect(vm.hasExternalUrls).toEqual(true);
+ });
+
+ it('should return false when deployment has no external_url_formatted', () => {
+ vm.deployment.external_url_formatted = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+
+ it('should return false when deployment has no external_url', () => {
+ vm.deployment.external_url = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentTime', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentTime).toEqual(true);
+ });
+
+ it('should return false when deployment has no deployed_at', () => {
+ vm.deployment.deployed_at = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+
+ it('should return false when deployment has no deployed_at_formatted', () => {
+ vm.deployment.deployed_at_formatted = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentMeta', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentMeta).toEqual(true);
+ });
+
+ it('should return false when deployment has no url', () => {
+ vm.deployment.url = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+
+ it('should return false when deployment has no name', () => {
+ vm.deployment.name = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('stopEnvironment', () => {
+ const url = '/foo/bar';
+ const returnPromise = () => new Promise((resolve) => {
+ resolve({
+ data: {
+ redirect_url: url,
+ },
+ });
+ });
+ const mockStopEnvironment = () => {
+ vm.stopEnvironment(deploymentMockData);
+ return vm;
+ };
+
+ it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
+ spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
+ setTimeout(() => {
+ expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
+ done();
+ }, 333);
+ });
+
+ it('should show a confirm dialog but should not work if the dialog is rejected', () => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let el;
+
+ beforeEach(() => {
+ vm = createComponent(deploymentMockData);
+ el = vm.$el;
+ });
+
+ it('renders deployment name', () => {
+ expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deploymentMockData.url);
+ expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+ });
+
+ it('renders external URL', () => {
+ expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url);
+ expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted);
+ });
+
+ it('renders stop button', () => {
+ expect(el.querySelector('.btn')).not.toBeNull();
+ });
+
+ it('renders deployment time', () => {
+ expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+ });
+
+ it('renders metrics component', () => {
+ expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
deleted file mode 100644
index 6a59dc3c87e..00000000000
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { getTimeago } from '~/lib/utils/datetime_utility';
-
-const deploymentMockData = [
- {
- id: 15,
- name: 'review/diplo',
- url: '/root/acets-review-apps/environments/15',
- stop_url: '/root/acets-review-apps/environments/15/stop',
- metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
- external_url: 'http://diplo.',
- external_url_formatted: 'diplo.',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- },
-];
-const createComponent = () => {
- const Component = Vue.extend(deploymentComponent);
- const mr = {
- deployments: deploymentMockData,
- };
- const service = {};
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
- });
-};
-
-describe('MRWidgetDeployment', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = deploymentComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
-
- expect(service.type instanceof Object).toBeTruthy();
- expect(service.required).toBeTruthy();
- });
- });
-
- describe('methods', () => {
- let vm = createComponent();
- const deployment = deploymentMockData[0];
-
- describe('formatDate', () => {
- it('should work', () => {
- const readable = getTimeago().format(deployment.deployed_at);
- expect(vm.formatDate(deployment.deployed_at)).toEqual(readable);
- });
- });
-
- describe('hasExternalUrls', () => {
- it('should return true', () => {
- expect(vm.hasExternalUrls(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasExternalUrls()).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url: 'Diplo' })).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentTime', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentTime(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentTime()).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentMeta', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentMeta(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentMeta()).toBeFalsy();
- expect(vm.hasDeploymentMeta({ url: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentMeta({ name: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('stopEnvironment', () => {
- const url = '/foo/bar';
- const returnPromise = () => new Promise((resolve) => {
- resolve({
- data: {
- redirect_url: url,
- },
- });
- });
- const mockStopEnvironment = () => {
- vm.stopEnvironment(deploymentMockData);
- return vm;
- };
-
- it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
- spyOn(window, 'confirm').and.returnValue(true);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
- spyOn(urlUtils, 'visitUrl').and.returnValue(true);
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
- setTimeout(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
- done();
- }, 333);
- });
-
- it('should show a confirm dialog but should not work if the dialog is rejected', () => {
- spyOn(window, 'confirm').and.returnValue(false);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
- });
- });
- });
-
- describe('template', () => {
- let vm;
- let el;
- const [deployment] = deploymentMockData;
-
- beforeEach(() => {
- vm = createComponent(deploymentMockData);
- el = vm.$el;
- });
-
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
- expect(el.querySelector('.js-icon-link')).toBeDefined();
- expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deployment.url);
- expect(el.querySelector('.js-deploy-meta').innerText).toContain(deployment.name);
- expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url);
- expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted);
- expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at));
- expect(el.querySelector('.js-mr-memory-usage')).toBeDefined();
- expect(el.querySelector('button')).toBeDefined();
- });
-
- it('should list multiple deployments', (done) => {
- vm.mr.deployments.push(deployment);
- vm.mr.deployments.push(deployment);
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.ci-widget').length).toEqual(3);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3);
- done();
- });
- });
-
- it('should not have some elements when there is not enough data', (done) => {
- vm.mr.deployments = [{}];
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0);
- expect(el.querySelectorAll('.button').length).toEqual(0);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 32876ca66ac..e55c7649d40 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -123,17 +123,6 @@ describe('mrWidgetOptions', () => {
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
});
-
- describe('shouldRenderDeployments', () => {
- it('should return false for the initial data', () => {
- expect(vm.shouldRenderDeployments).toBeFalsy();
- });
-
- it('should return true if there is deployments', () => {
- vm.mr.deployments.push({}, {});
- expect(vm.shouldRenderDeployments).toBeTruthy();
- });
- });
});
describe('methods', () => {
@@ -189,16 +178,16 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => {
it('should fetch deployments', (done) => {
- spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ deployment: 1 }]));
+ spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
vm.fetchDeployments();
setTimeout(() => {
expect(vm.service.fetchDeployments).toHaveBeenCalled();
expect(vm.mr.deployments.length).toEqual(1);
- expect(vm.mr.deployments[0].deployment).toEqual(1);
+ expect(vm.mr.deployments[0].id).toBe(1);
done();
- }, 333);
+ });
});
});
@@ -368,34 +357,6 @@ describe('mrWidgetOptions', () => {
});
});
- describe('components', () => {
- it('should register all components', () => {
- const comps = mrWidgetOptions.components;
- expect(comps['mr-widget-header']).toBeDefined();
- expect(comps['mr-widget-merge-help']).toBeDefined();
- expect(comps['mr-widget-pipeline']).toBeDefined();
- expect(comps['mr-widget-deployment']).toBeDefined();
- expect(comps['mr-widget-related-links']).toBeDefined();
- expect(comps['mr-widget-merged']).toBeDefined();
- expect(comps['mr-widget-closed']).toBeDefined();
- expect(comps['mr-widget-merging']).toBeDefined();
- expect(comps['mr-widget-failed-to-merge']).toBeDefined();
- expect(comps['mr-widget-wip']).toBeDefined();
- expect(comps['mr-widget-archived']).toBeDefined();
- expect(comps['mr-widget-conflicts']).toBeDefined();
- expect(comps['mr-widget-nothing-to-merge']).toBeDefined();
- expect(comps['mr-widget-not-allowed']).toBeDefined();
- expect(comps['mr-widget-missing-branch']).toBeDefined();
- expect(comps['mr-widget-ready-to-merge']).toBeDefined();
- expect(comps['mr-widget-checking']).toBeDefined();
- expect(comps['mr-widget-unresolved-discussions']).toBeDefined();
- expect(comps['mr-widget-pipeline-blocked']).toBeDefined();
- expect(comps['mr-widget-pipeline-failed']).toBeDefined();
- expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined();
- expect(comps['mr-widget-maintainer-edit']).toBeDefined();
- });
- });
-
describe('rendering relatedLinks', () => {
beforeEach((done) => {
vm.mr.relatedLinks = {
@@ -454,4 +415,34 @@ describe('mrWidgetOptions', () => {
});
});
});
+
+ describe('rendering deployments', () => {
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ };
+
+ beforeEach((done) => {
+ vm.mr.deployments.push({
+ ...deploymentMockData,
+ }, {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ });
+
+ vm.$nextTick(done);
+ });
+
+ it('renders multiple deployments', () => {
+ expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+ });
+ });
});
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index 9f42cf1dfca..5cb1f4deb5f 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -61,7 +61,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
before do
- gitlab_shell.add_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path)
repository = Rugged::Repository.new(repo_path)
repository.config['gitlab.fullpath'] = 'to/repo'
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 52c9876cbb6..54ada3e423f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -681,7 +681,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
subject { new_repository.fetch_repository_as_mirror(repository) }
before do
- Gitlab::Shell.new.add_repository('default', 'my_project')
+ Gitlab::Shell.new.create_repository('default', 'my_project')
end
after do
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 62ef93f847a..4a51777ba9b 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -43,7 +43,6 @@
{
"id": 40,
"title": "Voluptatem",
- "assignee_id": 1,
"author_id": 22,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.340Z",
@@ -61,7 +60,23 @@
"issue_assignees": [
{
"user_id": 1,
- "issue_id": 1
+ "issue_id": 40
+ },
+ {
+ "user_id": 15,
+ "issue_id": 40
+ },
+ {
+ "user_id": 16,
+ "issue_id": 40
+ },
+ {
+ "user_id": 16,
+ "issue_id": 40
+ },
+ {
+ "user_id": 6,
+ "issue_id": 40
}
],
"milestone": {
@@ -319,8 +334,7 @@
},
{
"id": 39,
- "title": "Delectus veniam ratione in eos culpa et natus molestiae earum aut.",
- "assignee_id": 20,
+ "title": "Issue without assignees",
"author_id": 22,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.233Z",
@@ -334,6 +348,7 @@
"confidential": false,
"due_date": null,
"moved_to_id": null,
+ "issue_assignees": [],
"milestone": {
"id": 1,
"title": "test milestone",
@@ -539,7 +554,6 @@
{
"id": 38,
"title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.",
- "assignee_id": 1,
"author_id": 6,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.154Z",
@@ -756,7 +770,6 @@
{
"id": 37,
"title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.",
- "assignee_id": 15,
"author_id": 20,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.051Z",
@@ -952,7 +965,6 @@
{
"id": 36,
"title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.",
- "assignee_id": 20,
"author_id": 16,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.958Z",
@@ -1148,7 +1160,6 @@
{
"id": 35,
"title": "Repellat praesentium deserunt maxime incidunt harum porro qui.",
- "assignee_id": 6,
"author_id": 20,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.832Z",
@@ -1344,7 +1355,6 @@
{
"id": 34,
"title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.",
- "assignee_id": 20,
"author_id": 1,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.717Z",
@@ -1540,7 +1550,6 @@
{
"id": 33,
"title": "Numquam accusamus eos iste exercitationem magni non inventore.",
- "assignee_id": 15,
"author_id": 26,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.611Z",
@@ -1736,7 +1745,6 @@
{
"id": 32,
"title": "Necessitatibus magnam qui at velit consequatur perspiciatis.",
- "assignee_id": 22,
"author_id": 15,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.431Z",
@@ -1932,7 +1940,6 @@
{
"id": 31,
"title": "Libero nam magnam incidunt eaque placeat error et.",
- "assignee_id": 1,
"author_id": 16,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.280Z",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index f4e466d1296..13e930bafe3 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -4,7 +4,12 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer do
describe 'restore project tree' do
before(:context) do
- @user = create(:user)
+ # Using an admin for import, so we can check assignment of existing members
+ @user = create(:admin)
+ @existing_members = [
+ create(:user, username: 'bernard_willms'),
+ create(:user, username: 'saul_will')
+ ]
RSpec::Mocks.with_temporary_scope do
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
@@ -63,8 +68,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
- it 'has issue assignees' do
- expect(Issue.where(title: 'Voluptatem').first.issue_assignees).not_to be_empty
+ it 'has multiple issue assignees' do
+ expect(Issue.find_by(title: 'Voluptatem').assignees).to contain_exactly(@user, *@existing_members)
+ expect(Issue.find_by(title: 'Issue without assignees').assignees).to be_empty
end
it 'contains the merge access levels on a protected branch' do
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index 10c5fb148cd..0b9b1f537b5 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -21,30 +21,77 @@ describe Gitlab::ProjectTransfer do
describe '#move_project' do
it "moves project upload to another namespace" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
+ describe '#move_namespace' do
+ context 'when moving namespace from root into another namespace' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, nil, @namespace_path)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when moving namespace from one parent to another' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, @namespace_path_was, @namespace_path)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when moving namespace from having a parent to root' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, @namespace_path_was, nil)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+ end
+
describe '#rename_project' do
it "renames project" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
+ path_to_be_moved = File.join(@root_dir, @namespace_path, @project_path_was)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
describe '#rename_namespace' do
it "renames namespace" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.rename_namespace(@namespace_path_was, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 56b45d8da3c..14b59c5e945 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Shell do
it { is_expected.to respond_to :add_key }
it { is_expected.to respond_to :remove_key }
- it { is_expected.to respond_to :add_repository }
+ it { is_expected.to respond_to :create_repository }
it { is_expected.to respond_to :remove_repository }
it { is_expected.to respond_to :fork_repository }
@@ -402,8 +402,8 @@ describe Gitlab::Shell do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#add_repository' do
- shared_examples '#add_repository' do
+ describe '#create_repository' do
+ shared_examples '#create_repository' do
let(:repository_storage) { 'default' }
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
let(:repo_name) { 'project/path' }
@@ -414,7 +414,7 @@ describe Gitlab::Shell do
end
it 'creates a repository' do
- expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy
expect(File.stat(created_path).mode & 0o777).to eq(0o770)
@@ -426,19 +426,19 @@ describe Gitlab::Shell do
it 'returns false when the command fails' do
FileUtils.mkdir_p(File.dirname(created_path))
# This file will block the creation of the repo's .git directory. That
- # should cause #add_repository to fail.
+ # should cause #create_repository to fail.
FileUtils.touch(created_path)
- expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy
end
end
context 'with gitaly' do
- it_behaves_like '#add_repository'
+ it_behaves_like '#create_repository'
end
context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#add_repository'
+ it_behaves_like '#create_repository'
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c27313ed88b..6e202de0db9 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1424,6 +1424,17 @@ describe Ci::Build do
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+ { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+ { key: 'CI_BUILD_REF', value: build.sha, public: true },
+ { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
+ { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true },
+ { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true },
+ { key: 'CI_BUILD_NAME', value: 'test', public: true },
+ { key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
@@ -1433,9 +1444,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
+ { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }
]
end
@@ -1834,39 +1843,6 @@ describe Ci::Build do
it { is_expected.to include(ci_config_path) }
end
- context 'returns variables in valid order' do
- let(:build_pre_var) { { key: 'build', value: 'value' } }
- let(:project_pre_var) { { key: 'project', value: 'value' } }
- let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
- let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
-
- before do
- allow(build).to receive(:predefined_variables) { [build_pre_var] }
- allow(build).to receive(:yaml_variables) { [build_yaml_var] }
-
- allow_any_instance_of(Project)
- .to receive(:predefined_variables) { [project_pre_var] }
-
- allow_any_instance_of(Project)
- .to receive(:secret_variables_for)
- .with(ref: 'master', environment: nil) do
- [create(:ci_variable, key: 'secret', value: 'value')]
- end
-
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:predefined_variables) { [pipeline_pre_var] }
- end
-
- it do
- is_expected.to eq(
- [build_pre_var,
- project_pre_var,
- pipeline_pre_var,
- build_yaml_var,
- { key: 'secret', value: 'value', public: false }])
- end
- end
-
context 'when using auto devops' do
context 'and is enabled' do
before do
@@ -1890,6 +1866,81 @@ describe Ci::Build do
end
end
end
+
+ context 'when pipeline variable overrides build variable' do
+ before do
+ build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
+ pipeline.variables.build(key: 'MYVAR', value: 'pipeline value')
+ end
+
+ it 'overrides YAML variable using a pipeline variable' do
+ variables = subject.reverse.uniq { |variable| variable[:key] }.reverse
+
+ expect(variables)
+ .not_to include(key: 'MYVAR', value: 'myvar', public: true)
+ expect(variables)
+ .to include(key: 'MYVAR', value: 'pipeline value', public: false)
+ end
+ end
+
+ describe 'variables ordering' do
+ context 'when variables hierarchy is stubbed' do
+ let(:build_pre_var) { { key: 'build', value: 'value' } }
+ let(:project_pre_var) { { key: 'project', value: 'value' } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
+
+ before do
+ allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+
+ allow_any_instance_of(Project)
+ .to receive(:predefined_variables) { [project_pre_var] }
+
+ allow_any_instance_of(Project)
+ .to receive(:secret_variables_for)
+ .with(ref: 'master', environment: nil) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
+
+ allow_any_instance_of(Ci::Pipeline)
+ .to receive(:predefined_variables) { [pipeline_pre_var] }
+ end
+
+ it 'returns variables in order depending on resource hierarchy' do
+ is_expected.to eq(
+ [build_pre_var,
+ project_pre_var,
+ pipeline_pre_var,
+ build_yaml_var,
+ { key: 'secret', value: 'value', public: false }])
+ end
+ end
+
+ context 'when build has environment and user-provided variables' do
+ let(:expected_variables) do
+ predefined_variables.map { |variable| variable.fetch(:key) } +
+ %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
+ CI_ENVIRONMENT_URL]
+ end
+
+ before do
+ create(:environment, project: build.project,
+ name: 'staging')
+
+ build.yaml_variables = [{ key: 'YAML_VARIABLE',
+ value: 'var',
+ public: true }]
+ build.environment = 'staging'
+ end
+
+ it 'matches explicit variables ordering' do
+ received_variables = subject.map { |variable| variable.fetch(:key) }
+
+ expect(received_variables).to eq expected_variables
+ end
+ end
+ end
end
describe 'state transition: any => [:pending]' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 14d234f6aab..86bb2fefae1 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -172,10 +172,10 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to be_an(Array) }
- it 'includes the defined keys' do
- keys = subject.map { |v| v[:key] }
+ it 'includes all predefined variables in a valid order' do
+ keys = subject.map { |variable| variable.fetch(:key) }
- expect(keys).to include('CI_PIPELINE_ID', 'CI_CONFIG_PATH', 'CI_PIPELINE_SOURCE')
+ expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE]
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e626efd054d..ee142718f7e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -204,43 +204,67 @@ describe Namespace do
expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy
end
- context 'with subgroups' do
+ context 'with subgroups', :nested_groups do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
+ let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) }
let(:uploads_dir) { FileUploader.root }
let(:pages_dir) { File.join(TestEnv.pages_path) }
+ def expect_project_directories_at(namespace_path)
+ expected_repository_path = File.join(TestEnv.repos_path, namespace_path, 'the-project.git')
+ expected_upload_path = File.join(uploads_dir, namespace_path, 'the-project')
+ expected_pages_path = File.join(pages_dir, namespace_path, 'the-project')
+
+ expect(File.directory?(expected_repository_path)).to be_truthy
+ expect(File.directory?(expected_upload_path)).to be_truthy
+ expect(File.directory?(expected_pages_path)).to be_truthy
+ end
+
before do
+ FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project.full_path}.git"))
FileUtils.mkdir_p(File.join(uploads_dir, project.full_path))
FileUtils.mkdir_p(File.join(pages_dir, project.full_path))
end
context 'renaming child' do
it 'correctly moves the repository, uploads and pages' do
- expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git')
- expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project')
- expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project')
+ child.update!(path: 'renamed')
- child.update_attributes!(path: 'renamed')
-
- expect(File.directory?(expected_repository_path)).to be(true)
- expect(File.directory?(expected_upload_path)).to be(true)
- expect(File.directory?(expected_pages_path)).to be(true)
+ expect_project_directories_at('parent/renamed')
end
end
context 'renaming parent' do
it 'correctly moves the repository, uploads and pages' do
- expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git')
- expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project')
- expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project')
+ parent.update!(path: 'renamed')
+
+ expect_project_directories_at('renamed/child')
+ end
+ end
+
+ context 'moving from one parent to another' do
+ it 'correctly moves the repository, uploads and pages' do
+ child.update!(parent: new_parent)
- parent.update_attributes!(path: 'renamed')
+ expect_project_directories_at('new_parent/child')
+ end
+ end
+
+ context 'moving from having a parent to root' do
+ it 'correctly moves the repository, uploads and pages' do
+ child.update!(parent: nil)
+
+ expect_project_directories_at('child')
+ end
+ end
+
+ context 'moving from root to having a parent' do
+ it 'correctly moves the repository, uploads and pages' do
+ parent.update!(parent: new_parent)
- expect(File.directory?(expected_repository_path)).to be(true)
- expect(File.directory?(expected_upload_path)).to be(true)
- expect(File.directory?(expected_pages_path)).to be(true)
+ expect_project_directories_at('new_parent/parent/child')
end
end
end
@@ -525,7 +549,6 @@ describe Namespace do
end
end
- # Note: Group transfers are not yet implemented
context 'when a group is transferred into a root group' do
context 'when the root group "Share with group lock" is enabled' do
let(:root_group) { create(:group, share_with_group_lock: true) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e970cd7dfdb..4cf8d861595 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1378,7 +1378,7 @@ describe Project do
context 'using a regular repository' do
it 'creates the repository' do
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(true)
@@ -1388,7 +1388,7 @@ describe Project do
end
it 'adds an error if the repository could not be created' do
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(false)
@@ -1402,7 +1402,7 @@ describe Project do
context 'using a forked repository' do
it 'does nothing' do
expect(project).to receive(:forked?).and_return(true)
- expect(shell).not_to receive(:add_repository)
+ expect(shell).not_to receive(:create_repository)
project.create_repository
end
@@ -1421,7 +1421,7 @@ describe Project do
allow(project).to receive(:repository_exists?)
.and_return(false)
- allow(shell).to receive(:add_repository)
+ allow(shell).to receive(:create_repository)
.with(project.repository_storage_path, project.disk_path)
.and_return(true)
@@ -1445,7 +1445,7 @@ describe Project do
allow(project).to receive(:repository_exists?)
.and_return(false)
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(true)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 8b4b5873704..d87c1ca14f0 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -74,7 +74,7 @@ describe ProjectWiki do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
gitlab_shell = double(:gitlab_shell)
- allow(gitlab_shell).to receive(:add_repository)
+ allow(gitlab_shell).to receive(:create_repository)
allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 9a44dfde41b..8471467d2fa 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -164,7 +164,7 @@ describe Projects::CreateService, '#execute' do
context 'with legacy storage' do
before do
- gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
end
after do
@@ -200,7 +200,7 @@ describe Projects::CreateService, '#execute' do
end
before do
- gitlab_shell.add_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path)
end
after do
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 409d5de8d43..d1011b07db6 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -108,7 +108,7 @@ describe Projects::ForkService do
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
before do
- gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+ gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
end
after do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index ae0e22e3dc0..ce567fe3879 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -151,7 +151,7 @@ describe Projects::TransferService do
before do
group.add_owner(user)
- unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}")
+ unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}")
raise 'failed to add repository'
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index d454ac0bda5..f3f97b6b921 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -196,7 +196,7 @@ describe Projects::UpdateService do
let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) }
before do
- gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
end
after do
diff --git a/yarn.lock b/yarn.lock
index 0d6e0a957b2..24e8c0eb3f6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6695,9 +6695,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-prettier@1.9.2:
- version "1.9.2"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827"
+prettier@1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
prettier@^1.7.0:
version "1.8.2"