summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.gitlab/issue_templates/Bug.md4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock10
-rw-r--r--README.md1
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js9
-rw-r--r--app/assets/javascripts/environments/components/environment.vue (renamed from app/assets/javascripts/environments/components/environment.js)153
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js21
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js21
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue (renamed from app/assets/javascripts/environments/folder/environments_folder_view.js)104
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js38
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js14
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js11
-rw-r--r--app/assets/javascripts/lib/utils/regexp.js10
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js7
-rw-r--r--app/assets/javascripts/pipelines/components/stage.js49
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.js101
-rw-r--r--app/assets/javascripts/pipelines/pipelines.js10
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines_store.js31
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js31
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss1
-rw-r--r--app/assets/stylesheets/framework/filters.scss22
-rw-r--r--app/helpers/application_helper.rb32
-rw-r--r--app/helpers/markup_helper.rb (renamed from app/helpers/gitlab_markdown_helper.rb)137
-rw-r--r--app/helpers/services_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb4
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/models/application_setting.rb9
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/network/graph.rb3
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/repository.rb14
-rw-r--r--app/models/service.rb3
-rw-r--r--app/policies/project_policy.rb44
-rw-r--r--app/services/projects/create_service.rb3
-rw-r--r--app/views/events/_event.atom.builder1
-rw-r--r--app/views/projects/_readme.html.haml3
-rw-r--r--app/views/projects/blob/_markup.html.haml2
-rw-r--r--app/views/projects/blob/preview.html.haml8
-rw-r--r--app/views/projects/boards/components/sidebar/_milestone.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/shared/icons/_mr_bold.svg3
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml2
-rw-r--r--app/views/shared/snippets/_blob.html.haml2
-rw-r--r--changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml4
-rw-r--r--changelogs/unreleased/26585-remove-readme-view-caching.yml4
-rw-r--r--changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml4
-rw-r--r--changelogs/unreleased/30466-click-x-to-remove-filter.yml4
-rw-r--r--changelogs/unreleased/30645-show-pipeline-events-description.yml4
-rw-r--r--changelogs/unreleased/30973-fix-network-graph-ordering.yml4
-rw-r--r--changelogs/unreleased/31138-improve-test-settings-for-services-in-empty-projects.yml4
-rw-r--r--changelogs/unreleased/31292-milestone-sidebar-display-incorect-number-of-mr-when-minimized.yml4
-rw-r--r--changelogs/unreleased/add-username-to-activity-feed.yml4
-rw-r--r--changelogs/unreleased/dm-sidekiq-5.yml4
-rw-r--r--changelogs/unreleased/fix-notify-post-receive.yml4
-rw-r--r--changelogs/unreleased/fix_emoji_parser.yml4
-rw-r--r--changelogs/unreleased/gl-version-backup-file.yml4
-rw-r--r--changelogs/unreleased/replace_header_mr_icon.yml4
-rw-r--r--changelogs/unreleased/tc-make-user-master-project-by-admin.yml4
-rw-r--r--changelogs/unreleased/zj-dockerfiles.yml4
-rw-r--r--db/migrate/20170421102337_remove_nil_type_services.rb12
-rw-r--r--db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb10
-rw-r--r--db/schema.rb2
-rw-r--r--doc/administration/high_availability/load_balancer.md3
-rw-r--r--doc/administration/high_availability/nfs.md19
-rw-r--r--doc/raketasks/backup_restore.md254
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md2
-rw-r--r--doc/user/project/integrations/bamboo.md10
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/backup/manager.rb2
-rw-r--r--lib/banzai/filter/emoji_filter.rb5
-rw-r--r--lib/github/import.rb38
-rw-r--r--lib/gitlab/asciidoc.rb20
-rw-r--r--lib/gitlab/data_builder/push.rb9
-rw-r--r--lib/gitlab/git/repository.rb23
-rw-r--r--lib/gitlab/other_markup.rb10
-rw-r--r--lib/gitlab/template/dockerfile_template.rb4
-rw-r--r--lib/tasks/gitlab/update_templates.rake8
-rw-r--r--spec/controllers/projects/services_controller_spec.rb49
-rw-r--r--spec/factories/services.rb13
-rw-r--r--spec/features/admin/admin_cohorts_spec.rb15
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb27
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb6
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb24
-rw-r--r--spec/fixtures/markdown.md.erb2
-rw-r--r--spec/helpers/application_helper_spec.rb27
-rw-r--r--spec/helpers/markup_helper_spec.rb (renamed from spec/helpers/gitlab_markdown_helper_spec.rb)35
-rw-r--r--spec/javascripts/environments/environment_spec.js5
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js4
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js63
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js16
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js7
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js106
-rw-r--r--spec/javascripts/pipelines/stage_spec.js15
-rw-r--r--spec/javascripts/pipelines/time_ago_spec.js64
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb10
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb21
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb29
-rw-r--r--spec/lib/gitlab/import_export/project.json41
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb1
-rw-r--r--spec/models/network/graph_spec.rb21
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb4
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/repository_spec.rb4
-rw-r--r--spec/models/service_spec.rb55
-rw-r--r--spec/requests/api/helpers_spec.rb28
-rw-r--r--spec/requests/api/v3/deployments_spec.rb12
-rw-r--r--spec/services/projects/create_service_spec.rb16
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--vendor/Dockerfile/CONTRIBUTING.md5
-rw-r--r--vendor/Dockerfile/HTTPd.Dockerfile (renamed from vendor/dockerfile/HTTPdDockerfile)0
-rw-r--r--vendor/Dockerfile/LICENSE21
-rw-r--r--vendor/Dockerfile/PHP.Dockerfile14
-rw-r--r--vendor/Dockerfile/Python2.Dockerfile11
123 files changed, 1452 insertions, 780 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5ea1d89e659..50f1f65e4e8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,6 +11,7 @@ variables:
NODE_ENV: "test"
SIMPLECOV: "true"
GIT_DEPTH: "20"
+ GIT_SUBMODULE_STRATEGY: "none"
PHANTOMJS_VERSION: "2.1.1"
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 6bb21e6a3af..241d90a204b 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -26,6 +26,7 @@ logs, and code as it's very hard to read otherwise.)
#### Results of GitLab environment info
<details>
+<pre>
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
@@ -33,11 +34,13 @@ logs, and code as it's very hard to read otherwise.)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
+</pre>
</details>
#### Results of GitLab application Check
<details>
+<pre>
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:check SANITIZE=true`)
@@ -47,6 +50,7 @@ logs, and code as it's very hard to read otherwise.)
(we will only investigate if the tests are passing)
+</pre>
</details>
### Possible fixes
diff --git a/Gemfile b/Gemfile
index decbcfe6375..41c2dcfd76e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -144,7 +144,7 @@ gem 'after_commit_queue', '~> 1.3.0'
gem 'acts-as-taggable-on', '~> 4.0'
# Background jobs
-gem 'sidekiq', '~> 4.2.7'
+gem 'sidekiq', '~> 5.0'
gem 'sidekiq-cron', '~> 0.4.4'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index fb11f590110..52707628748 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -429,7 +429,7 @@ GEM
multi_json (~> 1.10)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- mail (2.6.4)
+ mail (2.6.5)
mime-types (>= 1.16, < 4)
mail_room (0.9.1)
memoist (0.15.0)
@@ -603,7 +603,7 @@ GEM
json
recursive-open-struct (1.0.0)
redcarpet (3.4.0)
- redis (3.2.2)
+ redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
@@ -717,11 +717,11 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (4.2.10)
+ sidekiq (5.0.0)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
- redis (~> 3.2, >= 3.2.1)
+ redis (~> 3.3, >= 3.3.3)
sidekiq-cron (0.4.4)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
@@ -1007,7 +1007,7 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
- sidekiq (~> 4.2.7)
+ sidekiq (~> 5.0)
sidekiq-cron (~> 0.4.4)
sidekiq-limit_fetch (~> 3.4)
simplecov (~> 0.14.0)
diff --git a/README.md b/README.md
index 10d69efdc6b..59de828e1ac 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
[![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
+[![Gitter](https://badges.gitter.im/gitlabhq/gitlabhq.svg)](https://gitter.im/gitlabhq/gitlabhq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Test coverage
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 68a1c1de1df..e704be8b53e 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -106,15 +106,6 @@ export default Vue.component('pipelines-table', {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
- beforeUpdate() {
- if (this.state.pipelines.length &&
- this.$children &&
- !this.isMakingRequest &&
- !this.isLoading) {
- this.store.startTimeAgoLoops.call(this, Vue);
- }
- },
-
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.vue
index f7175e412da..f319d6ca0c8 100644
--- a/app/assets/javascripts/environments/components/environment.js
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -1,6 +1,7 @@
+<script>
+
/* eslint-disable no-new */
/* global Flash */
-import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
@@ -8,7 +9,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati
import '../../lib/utils/common_utils';
import eventHub from '../event_hub';
-export default Vue.component('environment-component', {
+export default {
components: {
'environment-table': EnvironmentTable,
@@ -140,76 +141,90 @@ export default Vue.component('environment-component', {
});
},
},
-
- template: `
- <div :class="cssContainerClass">
- <div class="top-area">
- <ul v-if="!isLoading" class="nav-links">
- <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
- <a :href="projectEnvironmentsPath">
- Available
- <span class="badge js-available-environments-count">
- {{state.availableCounter}}
- </span>
- </a>
- </li>
- <li v-bind:class="{ 'active' : scope === 'stopped' }">
- <a :href="projectStoppedEnvironmentsPath">
- Stopped
- <span class="badge js-stopped-environments-count">
- {{state.stoppedCounter}}
- </span>
- </a>
- </li>
- </ul>
- <div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-create">
- New environment
+};
+</script>
+<template>
+ <div :class="cssContainerClass">
+ <div class="top-area">
+ <ul
+ v-if="!isLoading"
+ class="nav-links">
+ <li :class="{ active: scope === null || scope === 'available' }">
+ <a :href="projectEnvironmentsPath">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li :class="{ active : scope === 'stopped' }">
+ <a :href="projectStoppedEnvironmentsPath">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
</a>
- </div>
+ </li>
+ </ul>
+ <div
+ v-if="canCreateEnvironmentParsed && !isLoading"
+ class="nav-controls">
+ <a
+ :href="newEnvironmentPath"
+ class="btn btn-create">
+ New environment
+ </a>
</div>
+ </div>
+
+ <div class="content-list environments-container">
+ <div
+ class="environments-list-loading text-center"
+ v-if="isLoading">
- <div class="content-list environments-container">
- <div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
- </div>
-
- <div class="blank-state blank-state-no-icon"
- v-if="!isLoading && state.environments.length === 0">
- <h2 class="blank-state-title js-blank-state-title">
- You don't have any environments right now.
- </h2>
- <p class="blank-state-text">
- Environments are places where code gets deployed, such as staging or production.
- <br />
- <a :href="helpPagePath">
- Read more about environments
- </a>
- </p>
-
- <a v-if="canCreateEnvironmentParsed"
- :href="newEnvironmentPath"
- class="btn btn-create js-new-environment-button">
- New Environment
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true" />
+ </div>
+
+ <div
+ class="blank-state blank-state-no-icon"
+ v-if="!isLoading && state.environments.length === 0">
+ <h2 class="blank-state-title js-blank-state-title">
+ You don't have any environments right now.
+ </h2>
+ <p class="blank-state-text">
+ Environments are places where code gets deployed, such as staging or production.
+ <br />
+ <a :href="helpPagePath">
+ Read more about environments
</a>
- </div>
-
- <div class="table-holder"
- v-if="!isLoading && state.environments.length > 0">
-
- <environment-table
- :environments="state.environments"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :service="service"
- :is-loading-folder-content="isLoadingFolderContent" />
- </div>
-
- <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
- :change="changePage"
- :pageInfo="state.paginationInformation">
- </table-pagination>
+ </p>
+
+ <a
+ v-if="canCreateEnvironmentParsed"
+ :href="newEnvironmentPath"
+ class="btn btn-create js-new-environment-button">
+ New Environment
+ </a>
</div>
+
+ <div
+ class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
+
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :service="service"
+ :is-loading-folder-content="isLoadingFolderContent" />
+ </div>
+
+ <table-pagination
+ v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation" />
</div>
- `,
-});
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/environments_bundle.js
index 8d963b335cf..c0662125f28 100644
--- a/app/assets/javascripts/environments/environments_bundle.js
+++ b/app/assets/javascripts/environments/environments_bundle.js
@@ -1,13 +1,10 @@
-import EnvironmentsComponent from './components/environment';
+import Vue from 'vue';
+import EnvironmentsComponent from './components/environment.vue';
-$(() => {
- window.gl = window.gl || {};
-
- if (gl.EnvironmentsListApp) {
- gl.EnvironmentsListApp.$destroy(true);
- }
-
- gl.EnvironmentsListApp = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
-});
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#environments-list-view',
+ components: {
+ 'environments-table-app': EnvironmentsComponent,
+ },
+ render: createElement => createElement('environments-table-app'),
+}));
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index f939eccf246..9add8c3d721 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,13 +1,10 @@
-import EnvironmentsFolderComponent from './environments_folder_view';
+import Vue from 'vue';
+import EnvironmentsFolderComponent from './environments_folder_view.vue';
-$(() => {
- window.gl = window.gl || {};
-
- if (gl.EnvironmentsListFolderApp) {
- gl.EnvironmentsListFolderApp.$destroy(true);
- }
-
- gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
- el: document.querySelector('#environments-folder-list-view'),
- });
-});
+document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#environments-folder-list-view',
+ components: {
+ 'environments-folder-app': EnvironmentsFolderComponent,
+ },
+ render: createElement => createElement('environments-folder-app'),
+}));
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 05d44f77d1d..d27b2acfcdf 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -1,6 +1,6 @@
+<script>
/* eslint-disable no-new */
/* global Flash */
-import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
@@ -8,7 +8,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
-export default Vue.component('environment-folder-view', {
+export default {
components: {
'environment-table': EnvironmentTable,
'table-pagination': TablePaginationComponent,
@@ -116,54 +116,66 @@ export default Vue.component('environment-folder-view', {
return param;
},
},
+};
+</script>
+<template>
+ <div :class="cssContainerClass">
+ <div
+ class="top-area"
+ v-if="!isLoading">
+
+ <h4 class="js-folder-name environments-folder-name">
+ Environments / <b>{{folderName}}</b>
+ </h4>
+
+ <ul class="nav-links">
+ <li :class="{ active: scope === null || scope === 'available' }">
+ <a
+ :href="availablePath"
+ class="js-available-environments-folder-tab">
+ Available
+ <span class="badge js-available-environments-count">
+ {{state.availableCounter}}
+ </span>
+ </a>
+ </li>
+ <li :class="{ active : scope === 'stopped' }">
+ <a
+ :href="stoppedPath"
+ class="js-stopped-environments-folder-tab">
+ Stopped
+ <span class="badge js-stopped-environments-count">
+ {{state.stoppedCounter}}
+ </span>
+ </a>
+ </li>
+ </ul>
+ </div>
- template: `
- <div :class="cssContainerClass">
- <div class="top-area" v-if="!isLoading">
-
- <h4 class="js-folder-name environments-folder-name">
- Environments / <b>{{folderName}}</b>
- </h4>
-
- <ul class="nav-links">
- <li v-bind:class="{ 'active': scope === null || scope === 'available' }">
- <a :href="availablePath" class="js-available-environments-folder-tab">
- Available
- <span class="badge js-available-environments-count">
- {{state.availableCounter}}
- </span>
- </a>
- </li>
- <li v-bind:class="{ 'active' : scope === 'stopped' }">
- <a :href="stoppedPath" class="js-stopped-environments-folder-tab">
- Stopped
- <span class="badge js-stopped-environments-count">
- {{state.stoppedCounter}}
- </span>
- </a>
- </li>
- </ul>
+ <div class="environments-container">
+ <div
+ class="environments-list-loading text-center"
+ v-if="isLoading">
+ <i
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"/>
</div>
- <div class="environments-container">
- <div class="environments-list-loading text-center" v-if="isLoading">
- <i class="fa fa-spinner fa-spin"></i>
- </div>
-
- <div class="table-holder"
- v-if="!isLoading && state.environments.length > 0">
+ <div
+ class="table-holder"
+ v-if="!isLoading && state.environments.length > 0">
- <environment-table
- :environments="state.environments"
- :can-create-deployment="canCreateDeploymentParsed"
- :can-read-environment="canReadEnvironmentParsed"
- :service="service"/>
+ <environment-table
+ :environments="state.environments"
+ :can-create-deployment="canCreateDeploymentParsed"
+ :can-read-environment="canReadEnvironmentParsed"
+ :service="service"/>
- <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
- :change="changePage"
- :pageInfo="state.paginationInformation"/>
- </div>
+ <table-pagination
+ v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
+ :change="changePage"
+ :pageInfo="state.paginationInformation"/>
</div>
</div>
- `,
-});
+ </div>
+</template>
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 68a832102a0..36af0674ac6 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -77,13 +77,14 @@ class FilteredSearchManager {
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
- this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
+ this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
+ this.removeTokenWrapper = this.removeToken.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
@@ -96,12 +97,13 @@ class FilteredSearchManager {
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.addEventListener('click', this.removeTokenWrapper);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
- document.addEventListener('keydown', this.removeSelectedTokenWrapper);
+ document.addEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -117,12 +119,13 @@ class FilteredSearchManager {
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.removeEventListener('click', this.removeTokenWrapper);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
- document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
+ document.removeEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
@@ -195,14 +198,28 @@ class FilteredSearchManager {
static selectToken(e) {
const button = e.target.closest('.selectable');
+ const removeButtonSelected = e.target.closest('.remove-token');
- if (button) {
+ if (!removeButtonSelected && button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
}
+ removeToken(e) {
+ const removeButtonSelected = e.target.closest('.remove-token');
+
+ if (removeButtonSelected) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const button = e.target.closest('.selectable');
+ gl.FilteredSearchVisualTokens.selectToken(button, true);
+ this.removeSelectedToken();
+ }
+ }
+
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
@@ -248,16 +265,21 @@ class FilteredSearchManager {
}
}
- removeSelectedToken(e) {
+ removeSelectedTokenKeydown(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
- gl.FilteredSearchVisualTokens.removeSelectedToken();
- this.handleInputPlaceholder();
- this.toggleClearSearchButton();
+ this.removeSelectedToken();
}
}
+ removeSelectedToken() {
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+ this.handleInputPlaceholder();
+ this.toggleClearSearchButton();
+ this.dropdownManager.updateCurrentDropdownOffset();
+ }
+
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index a5657fc8720..453ecccc6fc 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -16,11 +16,11 @@ class FilteredSearchVisualTokens {
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
- static selectToken(tokenButton) {
+ static selectToken(tokenButton, forceSelection = false) {
const selected = tokenButton.classList.contains('selected');
FilteredSearchVisualTokens.unselectTokens();
- if (!selected) {
+ if (!selected || forceSelection) {
tokenButton.classList.add('selected');
}
}
@@ -38,7 +38,12 @@ class FilteredSearchVisualTokens {
return `
<div class="selectable" role="button">
<div class="name"></div>
- <div class="value"></div>
+ <div class="value-container">
+ <div class="value"></div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
</div>
`;
}
@@ -122,7 +127,8 @@ class FilteredSearchVisualTokens {
if (value) {
const button = lastVisualToken.querySelector('.selectable');
- button.removeChild(value);
+ const valueContainer = lastVisualToken.querySelector('.value-container');
+ button.removeChild(valueContainer);
lastVisualToken.innerHTML = button.innerHTML;
} else {
lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index b62b2cec4d8..687a462a0d4 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -3,6 +3,7 @@
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from '~/behaviors/gl_emoji';
+import glRegexp from '~/lib/utils/regexp';
// Creates the variables for setting up GFM auto-completion
window.gl = window.gl || {};
@@ -127,7 +128,15 @@ window.gl.GfmAutoComplete = {
callbacks: {
sorter: this.DefaultOptions.sorter,
beforeInsert: this.DefaultOptions.beforeInsert,
- filter: this.DefaultOptions.filter
+ filter: this.DefaultOptions.filter,
+
+ 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);
+
+ return match && match.length ? match[1] : null;
+ }
}
});
// Team Members
diff --git a/app/assets/javascripts/lib/utils/regexp.js b/app/assets/javascripts/lib/utils/regexp.js
new file mode 100644
index 00000000000..baa0b51d59b
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/regexp.js
@@ -0,0 +1,10 @@
+/**
+ * Regexp utility for the convenience of working with regular expressions.
+ *
+ */
+
+// Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203
+// Unicode 6.1
+const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
+
+export default { unicodeLetters };
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index 9c58c465001..64c1447f427 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -28,7 +28,9 @@ export default class MiniPipelineGraph {
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
- $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
+ $(document)
+ .off('shown.bs.dropdown', this.container)
+ .on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
@@ -91,6 +93,9 @@ export default class MiniPipelineGraph {
},
error: () => {
this.toggleLoading(button);
+ if ($(button).parent().hasClass('open')) {
+ $(button).dropdown('toggle');
+ }
new Flash('An error occurred while fetching the builds.', 'alert');
},
});
diff --git a/app/assets/javascripts/pipelines/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js
index b8cc3630611..203485f2990 100644
--- a/app/assets/javascripts/pipelines/components/stage.js
+++ b/app/assets/javascripts/pipelines/components/stage.js
@@ -2,13 +2,6 @@
import StatusIconEntityMap from '../../ci_status_icons';
export default {
- data() {
- return {
- builds: '',
- spinner: '<span class="fa fa-spinner fa-spin"></span>',
- };
- },
-
props: {
stage: {
type: Object,
@@ -16,6 +9,13 @@ export default {
},
},
+ data() {
+ return {
+ builds: '',
+ spinner: '<span class="fa fa-spinner fa-spin"></span>',
+ };
+ },
+
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
@@ -31,7 +31,13 @@ export default {
return this.$http.get(this.stage.dropdown_path)
.then((response) => {
this.builds = JSON.parse(response.body).html;
- }, () => {
+ })
+ .catch(() => {
+ // If dropdown is opened we'll close it.
+ if (this.$el.classList.contains('open')) {
+ $(this.$refs.dropdown).dropdown('toggle');
+ }
+
const flash = new Flash('Something went wrong on our end.');
return flash;
});
@@ -46,9 +52,10 @@ export default {
* target the click event of this component.
*/
stopDropdownClickPropagation() {
- $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
- e.stopPropagation();
- });
+ $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
+ .on('click', (e) => {
+ e.stopPropagation();
+ });
},
},
computed: {
@@ -81,12 +88,22 @@ export default {
data-placement="top"
data-toggle="dropdown"
type="button"
- :aria-label="stage.title">
- <span v-html="svgHTML" aria-hidden="true"></span>
- <i class="fa fa-caret-down" aria-hidden="true"></i>
+ :aria-label="stage.title"
+ ref="dropdown">
+ <span
+ v-html="svgHTML"
+ aria-hidden="true">
+ </span>
+ <i
+ class="fa fa-caret-down"
+ aria-hidden="true" />
</button>
- <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
- <div class="arrow-up" aria-hidden="true"></div>
+ <ul
+ ref="dropdown-content"
+ class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+ <div
+ class="arrow-up"
+ aria-hidden="true"></div>
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
diff --git a/app/assets/javascripts/pipelines/components/time_ago.js b/app/assets/javascripts/pipelines/components/time_ago.js
index 498d0715f54..188f74cc705 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.js
+++ b/app/assets/javascripts/pipelines/components/time_ago.js
@@ -2,68 +2,95 @@ import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility';
export default {
+ props: {
+ finishedTime: {
+ type: String,
+ required: true,
+ },
+
+ duration: {
+ type: Number,
+ required: true,
+ },
+ },
+
data() {
return {
- currentTime: new Date(),
iconTimerSvg,
};
},
- props: ['pipeline'],
+
+ updated() {
+ $(this.$refs.tooltip).tooltip('fixTitle');
+ },
+
computed: {
- timeAgo() {
- return gl.utils.getTimeago();
+ hasDuration() {
+ return this.duration > 0;
},
- localTimeFinished() {
- return gl.utils.formatDate(this.pipeline.details.finished_at);
+
+ hasFinishedTime() {
+ return this.finishedTime !== '';
},
- timeStopped() {
- const changeTime = this.currentTime;
- const options = {
- weekday: 'long',
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- };
- options.timeZoneName = 'short';
- const finished = this.pipeline.details.finished_at;
- if (!finished && changeTime) return false;
- return ({ words: this.timeAgo.format(finished) });
+
+ localTimeFinished() {
+ return gl.utils.formatDate(this.finishedTime);
},
- duration() {
- const { duration } = this.pipeline.details;
- const date = new Date(duration * 1000);
+
+ durationFormated() {
+ const date = new Date(this.duration * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
- if (hh < 10) hh = `0${hh}`;
- if (mm < 10) mm = `0${mm}`;
- if (ss < 10) ss = `0${ss}`;
+ // left pad
+ if (hh < 10) {
+ hh = `0${hh}`;
+ }
+ if (mm < 10) {
+ mm = `0${mm}`;
+ }
+ if (ss < 10) {
+ ss = `0${ss}`;
+ }
- if (duration !== null) return `${hh}:${mm}:${ss}`;
- return false;
+ return `${hh}:${mm}:${ss}`;
},
- },
- methods: {
- changeTime() {
- this.currentTime = new Date();
+
+ finishedTimeFormated() {
+ const timeAgo = gl.utils.getTimeago();
+
+ return timeAgo.format(this.finishedTime);
},
},
+
template: `
<td class="pipelines-time-ago">
- <p class="duration" v-if='duration'>
- <span v-html="iconTimerSvg"></span>
- {{duration}}
+ <p
+ class="duration"
+ v-if="hasDuration">
+ <span
+ v-html="iconTimerSvg">
+ </span>
+ {{durationFormated}}
</p>
- <p class="finished-at" v-if='timeStopped'>
- <i class="fa fa-calendar"></i>
+
+ <p
+ class="finished-at"
+ v-if="hasFinishedTime">
+
+ <i
+ class="fa fa-calendar"
+ aria-hidden="true" />
+
<time
+ ref="tooltip"
data-toggle="tooltip"
data-placement="top"
data-container="body"
- :data-original-title='localTimeFinished'>
- {{timeStopped.words}}
+ :title="localTimeFinished">
+ {{finishedTimeFormated}}
</time>
</p>
</td>
diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js
index 6eea4812f33..93d4818231f 100644
--- a/app/assets/javascripts/pipelines/pipelines.js
+++ b/app/assets/javascripts/pipelines/pipelines.js
@@ -1,4 +1,3 @@
-import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
@@ -161,15 +160,6 @@ export default {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
- beforeUpdate() {
- if (this.state.pipelines.length &&
- this.$children &&
- !this.isMakingRequest &&
- !this.isLoading) {
- this.store.startTimeAgoLoops.call(this, Vue);
- }
- },
-
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js
index 377ec8ba2cc..ffefe0192f2 100644
--- a/app/assets/javascripts/pipelines/stores/pipelines_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js
@@ -1,6 +1,3 @@
-/* eslint-disable no-underscore-dangle*/
-import VueRealtimeListener from '../../vue_realtime_listener';
-
export default class PipelinesStore {
constructor() {
this.state = {};
@@ -30,32 +27,4 @@ export default class PipelinesStore {
this.state.pageInfo = paginationInfo;
}
-
- /**
- * FIXME: Move this inside the component.
- *
- * Once the data is received we will start the time ago loops.
- *
- * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
- * update the time to show how long as passed.
- *
- */
- startTimeAgoLoops() {
- const startTimeLoops = () => {
- this.timeLoopInterval = setInterval(() => {
- this.$children[0].$children.reduce((acc, component) => {
- const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
- acc.push(timeAgoComponent);
- return acc;
- }, []).forEach(e => e.changeTime());
- }, 10000);
- };
-
- startTimeLoops();
-
- const removeIntervals = () => clearInterval(this.timeLoopInterval);
- const startIntervals = () => startTimeLoops();
-
- VueRealtimeListener(removeIntervals, startIntervals);
- }
}
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js b/app/assets/javascripts/vue_realtime_listener/index.js
deleted file mode 100644
index 4ddb2f975b0..00000000000
--- a/app/assets/javascripts/vue_realtime_listener/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default (removeIntervals, startIntervals) => {
- window.removeEventListener('focus', startIntervals);
- window.removeEventListener('blur', removeIntervals);
- window.removeEventListener('onbeforeload', removeIntervals);
-
- window.addEventListener('focus', startIntervals);
- window.addEventListener('blur', removeIntervals);
- window.addEventListener('onbeforeload', removeIntervals);
-};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index 62b7131de51..79806bc7204 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -1,5 +1,4 @@
/* eslint-disable no-param-reassign */
-
import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
@@ -166,6 +165,32 @@ export default {
}
return undefined;
},
+
+ /**
+ * Timeago components expects a number
+ *
+ * @return {type} description
+ */
+ pipelineDuration() {
+ if (this.pipeline.details && this.pipeline.details.duration) {
+ return this.pipeline.details.duration;
+ }
+
+ return 0;
+ },
+
+ /**
+ * Timeago component expects a String.
+ *
+ * @return {String}
+ */
+ pipelineFinishedAt() {
+ if (this.pipeline.details && this.pipeline.details.finished_at) {
+ return this.pipeline.details.finished_at;
+ }
+
+ return '';
+ },
},
template: `
@@ -192,7 +217,9 @@ export default {
</div>
</td>
- <time-ago :pipeline="pipeline"/>
+ <time-ago
+ :duration="pipelineDuration"
+ :finished-time="pipelineFinishedAt" />
<td class="pipeline-actions">
<div class="pull-right btn-group">
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 30d785464ac..1313ea25c2a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -195,7 +195,6 @@
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
- overflow: hidden;
@include set-invisible;
@media (max-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 11d44df4867..0692f65043b 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -104,6 +104,24 @@
padding: 2px 7px;
}
+ .value {
+ padding-right: 0;
+ }
+
+ .remove-token {
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 8px;
+
+ .fa-close {
+ color: $gl-text-color-disabled;
+ }
+
+ &:hover .fa-close {
+ color: $gl-text-color-secondary;
+ }
+ }
+
.name {
background-color: $filter-name-resting-color;
color: $filter-name-text-color;
@@ -112,7 +130,7 @@
text-transform: capitalize;
}
- .value {
+ .value-container {
background-color: $white-normal;
color: $filter-value-text-color;
border-radius: 0 2px 2px 0;
@@ -124,7 +142,7 @@
background-color: $filter-name-selected-color;
}
- .value {
+ .value-container {
background-color: $filter-value-selected-color;
}
}
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e5b811f3300..fff57472a4f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -196,38 +196,6 @@ module ApplicationHelper
end
end
- def render_markup(file_name, file_content)
- if gitlab_markdown?(file_name)
- Hamlit::RailsHelpers.preserve(markdown(file_content))
- elsif asciidoc?(file_name)
- asciidoc(file_content)
- elsif plain?(file_name)
- content_tag :pre, class: 'plain-readme' do
- file_content
- end
- else
- other_markup(file_name, file_content)
- end
- rescue RuntimeError
- simple_format(file_content)
- end
-
- def plain?(filename)
- Gitlab::MarkupHelper.plain?(filename)
- end
-
- def markup?(filename)
- Gitlab::MarkupHelper.markup?(filename)
- end
-
- def gitlab_markdown?(filename)
- Gitlab::MarkupHelper.gitlab_markdown?(filename)
- end
-
- def asciidoc?(filename)
- Gitlab::MarkupHelper.asciidoc?(filename)
- end
-
def promo_host
'about.gitlab.com'
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/markup_helper.rb
index 106feb87398..0781874d7fc 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -1,6 +1,22 @@
require 'nokogiri'
-module GitlabMarkdownHelper
+module MarkupHelper
+ def plain?(filename)
+ Gitlab::MarkupHelper.plain?(filename)
+ end
+
+ def markup?(filename)
+ Gitlab::MarkupHelper.markup?(filename)
+ end
+
+ def gitlab_markdown?(filename)
+ Gitlab::MarkupHelper.gitlab_markdown?(filename)
+ end
+
+ def asciidoc?(filename)
+ Gitlab::MarkupHelper.asciidoc?(filename)
+ end
+
# Use this in places where you would normally use link_to(gfm(...), ...).
#
# It solves a problem occurring with nested links (i.e.
@@ -11,7 +27,7 @@ module GitlabMarkdownHelper
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
- return "" if body.blank?
+ return '' if body.blank?
context = {
project: @project,
@@ -43,71 +59,73 @@ module GitlabMarkdownHelper
fragment.to_html.html_safe
end
+ # Return the first line of +text+, up to +max_chars+, after parsing the line
+ # as Markdown. HTML tags in the parsed output are not counted toward the
+ # +max_chars+ limit. If the length limit falls within a tag's contents, then
+ # the tag contents are truncated without removing the closing tag.
+ def first_line_in_markdown(text, max_chars = nil, options = {})
+ md = markdown(text, options).strip
+
+ truncate_visible(md, max_chars || md.length) if md.present?
+ end
+
def markdown(text, context = {})
- return "" unless text.present?
+ return '' unless text.present?
context[:project] ||= @project
-
- html = Banzai.render(text, context)
+ html = markdown_unsafe(text, context)
banzai_postprocess(html, context)
end
def markdown_field(object, field)
object = object.for_display if object.respond_to?(:for_display)
- return "" unless object.present?
+ return '' unless object.present?
html = Banzai.render_field(object, field)
banzai_postprocess(html, object.banzai_render_context(field))
end
- def asciidoc(text)
- Gitlab::Asciidoc.render(
- text,
- project: @project,
- current_user: (current_user if defined?(current_user)),
-
- # RelativeLinkFilter
- project_wiki: @project_wiki,
- requested_path: @path,
- ref: @ref,
- commit: @commit
- )
+ def markup(file_name, text, context = {})
+ context[:project] ||= @project
+ html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
+ banzai_postprocess(html, context)
end
- def other_markup(file_name, text)
- Gitlab::OtherMarkup.render(
- file_name,
- text,
- project: @project,
- current_user: (current_user if defined?(current_user)),
+ def render_wiki_content(wiki_page)
+ text = wiki_page.content
+ return '' unless text.present?
+
+ context = { pipeline: :wiki, project: @project, project_wiki: @project_wiki, page_slug: wiki_page.slug }
+
+ html =
+ case wiki_page.format
+ when :markdown
+ markdown_unsafe(text, context)
+ when :asciidoc
+ asciidoc_unsafe(text)
+ else
+ wiki_page.formatted_content.html_safe
+ end
- # RelativeLinkFilter
- project_wiki: @project_wiki,
- requested_path: @path,
- ref: @ref,
- commit: @commit
- )
+ banzai_postprocess(html, context)
end
- # Return the first line of +text+, up to +max_chars+, after parsing the line
- # as Markdown. HTML tags in the parsed output are not counted toward the
- # +max_chars+ limit. If the length limit falls within a tag's contents, then
- # the tag contents are truncated without removing the closing tag.
- def first_line_in_markdown(text, max_chars = nil, options = {})
- md = markdown(text, options).strip
+ def markup_unsafe(file_name, text, context = {})
+ return '' unless text.present?
- truncate_visible(md, max_chars || md.length) if md.present?
- end
-
- def render_wiki_content(wiki_page)
- case wiki_page.format
- when :markdown
- markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki, page_slug: wiki_page.slug)
- when :asciidoc
- asciidoc(wiki_page.content)
+ if gitlab_markdown?(file_name)
+ Hamlit::RailsHelpers.preserve(markdown_unsafe(text, context))
+ elsif asciidoc?(file_name)
+ asciidoc_unsafe(text)
+ elsif plain?(file_name)
+ content_tag :pre, class: 'plain-readme' do
+ text
+ end
else
- wiki_page.formatted_content.html_safe
+ other_markup_unsafe(file_name, text)
end
+ rescue RuntimeError
+ simple_format(text)
end
# Returns the text necessary to reference `entity` across projects
@@ -183,10 +201,10 @@ module GitlabMarkdownHelper
end
def markdown_toolbar_button(options = {})
- data = options[:data].merge({ container: "body" })
+ data = options[:data].merge({ container: 'body' })
content_tag :button,
- type: "button",
- class: "toolbar-btn js-md has-tooltip hidden-xs",
+ type: 'button',
+ class: 'toolbar-btn js-md has-tooltip hidden-xs',
tabindex: -1,
data: data,
title: options[:title],
@@ -195,17 +213,34 @@ module GitlabMarkdownHelper
end
end
+ def markdown_unsafe(text, context = {})
+ Banzai.render(text, context)
+ end
+
+ def asciidoc_unsafe(text)
+ Gitlab::Asciidoc.render(text)
+ end
+
+ def other_markup_unsafe(file_name, text)
+ Gitlab::OtherMarkup.render(file_name, text)
+ end
+
# Calls Banzai.post_process with some common context options
def banzai_postprocess(html, context = {})
+ return '' unless html.present?
+
context.merge!(
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
- requested_path: @path,
+ commit: @commit,
project_wiki: @project_wiki,
- ref: @ref
+ ref: @ref,
+ requested_path: @path
)
Banzai.post_process(html, context)
end
+
+ extend self
end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 715e5893a2c..3707bb5ba36 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -13,8 +13,8 @@ module ServicesHelper
"Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged"
- when "build", "build_events"
- "Event will be triggered when a build status changes"
+ when "pipeline", "pipeline_events"
+ "Event will be triggered when a pipeline status changes"
when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index f1dab60524e..f7b5a5f4dfc 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -12,10 +12,6 @@ module TreeHelper
tree.html_safe
end
- def render_readme(readme)
- render_markup(readme.name, readme.data)
- end
-
# Return an image icon depending on the file type and mode
#
# type - String type of the tree item; either 'folder' or 'file'
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index a9b6b33eb5c..d2980db218a 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,6 +1,6 @@
class BaseMailer < ActionMailer::Base
helper ApplicationHelper
- helper GitlabMarkdownHelper
+ helper MarkupHelper
attr_accessor :current_user
helper_method :current_user, :can?
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index dd1a6922968..cf042717c95 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -28,6 +28,8 @@ class ApplicationSetting < ActiveRecord::Base
attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
+ validates :uuid, presence: true
+
validates :session_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -159,6 +161,7 @@ class ApplicationSetting < ActiveRecord::Base
end
end
+ before_validation :ensure_uuid!
before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
@@ -344,6 +347,12 @@ class ApplicationSetting < ActiveRecord::Base
private
+ def ensure_uuid!
+ return if uuid?
+
+ self.uuid = SecureRandom.uuid
+ end
+
def check_repository_storages
invalid = repository_storages - Gitlab.config.repositories.storages.keys
errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
diff --git a/app/models/event.rb b/app/models/event.rb
index 5c34844b5d3..b780c1faf81 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -16,7 +16,7 @@ class Event < ActiveRecord::Base
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
- delegate :name, :email, :public_email, to: :author, prefix: true, allow_nil: true
+ delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true
delegate :title, to: :merge_request, prefix: true, allow_nil: true
delegate :title, to: :note, prefix: true, allow_nil: true
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 0bbc9451ffd..59737bb6085 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -107,7 +107,8 @@ module Network
def find_commits(skip = 0)
opts = {
max_count: self.class.max_count,
- skip: skip
+ skip: skip,
+ order: :date
}
opts[:ref] = @commit.id if @filter_ref
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index f2dfb87dbda..fa782c6fbb7 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -22,7 +22,7 @@ class ChatNotificationService < Service
end
def can_test?
- super && valid?
+ valid?
end
def self.supported_events
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7bb874d7744..e74edb8e6f7 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -17,9 +17,9 @@ class Repository
# same name. The cache key used by those methods must also match method's
# name.
#
- # For example, for entry `:readme` there's a method called `readme` which
- # stores its data in the `readme` cache key.
- CACHED_METHODS = %i(size commit_count readme contribution_guide
+ # For example, for entry `:commit_count` there's a method called `commit_count` which
+ # stores its data in the `commit_count` cache key.
+ CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? empty? root_ref).freeze
@@ -28,7 +28,7 @@ class Repository
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
# the corresponding methods to call for refreshing caches.
METHOD_CACHES_FOR_FILE_TYPES = {
- readme: :readme,
+ readme: :rendered_readme,
changelog: :changelog,
license: %i(license_blob license_key),
contributing: :contribution_guide,
@@ -527,7 +527,11 @@ class Repository
head.readme
end
end
- cache_method :readme
+
+ def rendered_readme
+ MarkupHelper.markup_unsafe(readme.name, readme.data, project: project) if readme
+ end
+ cache_method :rendered_readme
def contribution_guide
file_on_head(:contributing)
diff --git a/app/models/service.rb b/app/models/service.rb
index dc76bf925d3..c71a7d169ec 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -26,6 +26,7 @@ class Service < ActiveRecord::Base
has_one :service_hook
validates :project_id, presence: true, unless: proc { |service| service.template? }
+ validates :type, presence: true
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
@@ -131,7 +132,7 @@ class Service < ActiveRecord::Base
end
def can_test?
- !project.empty_repo?
+ true
end
# reason why service cannot be tested
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f8594e29547..5baac9ebe4b 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -2,20 +2,13 @@ class ProjectPolicy < BasePolicy
def rules
team_access!(user)
- owner = project.owner == user ||
- (project.group && project.group.has_owner?(user))
-
- owner_access! if user.admin? || owner
- team_member_owner_access! if owner
+ owner_access! if user.admin? || owner?
+ team_member_owner_access! if owner?
if project.public? || (project.internal? && !user.external?)
guest_access!
public_access!
-
- if project.request_access_enabled &&
- !(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
- can! :request_access
- end
+ can! :request_access if access_requestable?
end
archived_access! if project.archived?
@@ -27,6 +20,13 @@ class ProjectPolicy < BasePolicy
@subject
end
+ def owner?
+ return @owner if defined?(@owner)
+
+ @owner = project.owner == user ||
+ (project.group && project.group.has_owner?(user))
+ end
+
def guest_access!
can! :read_project
can! :read_board
@@ -226,14 +226,6 @@ class ProjectPolicy < BasePolicy
disabled_features!
end
- def project_group_member?(user)
- project.group &&
- (
- project.group.members_with_parents.exists?(user_id: user.id) ||
- project.group.requesters.exists?(user_id: user.id)
- )
- end
-
def block_issues_abilities
unless project.feature_available?(:issues, user)
cannot! :read_issue if project.default_issues_tracker?
@@ -254,6 +246,22 @@ class ProjectPolicy < BasePolicy
private
+ def project_group_member?(user)
+ project.group &&
+ (
+ project.group.members_with_parents.exists?(user_id: user.id) ||
+ project.group.requesters.exists?(user_id: user.id)
+ )
+ end
+
+ def access_requestable?
+ project.request_access_enabled &&
+ !owner? &&
+ !user.admin? &&
+ !project.team.member?(user) &&
+ !project_group_member?(user)
+ end
+
# A base set of abilities for read-only users, which
# is then augmented as necessary for anonymous and other
# read-only users.
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 7828c5806b0..535d93385e6 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -97,7 +97,8 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group || @project.gitlab_project_import?
- @project.team << [current_user, :master, current_user]
+ owners = [current_user, @project.namespace.owner].compact.uniq
+ @project.add_master(owners, current_user: current_user)
end
@project.group&.refresh_members_authorized_projects
diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder
index 158061579f6..e2aec532a9d 100644
--- a/app/views/events/_event.atom.builder
+++ b/app/views/events/_event.atom.builder
@@ -8,6 +8,7 @@ xml.entry do
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email))
xml.author do
+ xml.username event.author_username
xml.name event.author_name
xml.email event.author_public_email
end
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index b6fb08b68e9..c0d12cbc66e 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -4,8 +4,7 @@
- if can?(current_user, :push_code, @project)
= link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light edit-project-readme'
.file-content.wiki
- = cache(readme_cache_key) do
- = render_readme(readme)
+ = markup(readme.name, readme.data, rendered: @repository.rendered_readme)
- else
.row-content-block.second-block.center
%h3.page-title
diff --git a/app/views/projects/blob/_markup.html.haml b/app/views/projects/blob/_markup.html.haml
index 4ee4b03ff04..0090f7a11df 100644
--- a/app/views/projects/blob/_markup.html.haml
+++ b/app/views/projects/blob/_markup.html.haml
@@ -1,4 +1,4 @@
- blob.load_all_data!(@repository)
.file-content.wiki
- = render_markup(blob.name, blob.data)
+ = markup(blob.name, blob.data)
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index 5cafb644b40..e87b73c9a34 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,12 +1,8 @@
.diff-file
.diff-content
- - if gitlab_markdown?(@blob.name)
+ - if markup?(@blob.name)
.file-content.wiki
- = preserve do
- = markdown(@content)
- - elsif markup?(@blob.name)
- .file-content.wiki
- = raw render_markup(@blob.name, @content)
+ = markup(@blob.name, @content)
- else
.file-content.code.js-syntax-highlight
- unless @diff_lines.empty?
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml
index 008d1186478..190e7290303 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml
@@ -22,7 +22,7 @@
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
- = dropdown_title("Assignee milestone")
+ = dropdown_title("Assign milestone")
= dropdown_filter("Search milestones")
= dropdown_content
= dropdown_loading
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index bdcc160a067..01599060844 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -5,4 +5,4 @@
%strong
= readme.name
.file-content.wiki
- = render_readme(readme)
+ = markup(readme.name, readme.data)
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index f84be600df8..f2fe5742c12 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -21,7 +21,7 @@
.file-content.wiki
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
- = render_markup(snippet.file_name, chunk[:data])
+ = markup(snippet.file_name, chunk[:data])
- else
.file-content.code
.nothing-here-block Empty file
diff --git a/app/views/shared/icons/_mr_bold.svg b/app/views/shared/icons/_mr_bold.svg
index 2daa55a8652..5468545da2e 100644
--- a/app/views/shared/icons/_mr_bold.svg
+++ b/app/views/shared/icons/_mr_bold.svg
@@ -1 +1,2 @@
-<svg width="15" height="20" viewBox="0 0 12 14" xmlns="http://www.w3.org/2000/svg"><path d="M1 4.967a2.15 2.15 0 1 1 2.3 0v5.066a2.15 2.15 0 1 1-2.3 0V4.967zm7.85 5.17V5.496c0-.745-.603-1.346-1.35-1.346V6l-3-3 3-3v1.85c2.016 0 3.65 1.63 3.65 3.646v4.45a2.15 2.15 0 1 1-2.3.191z" fill-rule="nonzero"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg>
+
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index ccc808ff43e..774d20fb5ba 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -89,7 +89,7 @@
.sidebar-collapsed-icon
%strong
= icon('exclamation', 'aria-hidden': 'true')
- %span= milestone.issues_visible_to_user(current_user).count
+ %span= milestone.merge_requests.count
.title.hide-collapsed
Merge requests
%span.badge= milestone.merge_requests.count
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 74f71e6cbd1..895c3f1e99d 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -24,6 +24,6 @@
- if gitlab_markdown?(@snippet.file_name)
= preserve(markdown_field(@snippet, :content))
- else
- = render_markup(@snippet.file_name, @snippet.content)
+ = markup(@snippet.file_name, @snippet.content)
- else
= render 'shared/file_highlight', blob: @snippet
diff --git a/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml b/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml
new file mode 100644
index 00000000000..70d35f06af4
--- /dev/null
+++ b/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml
@@ -0,0 +1,4 @@
+---
+title: Lazily sets UUID in ApplicationSetting for new installations
+merge_request:
+author:
diff --git a/changelogs/unreleased/26585-remove-readme-view-caching.yml b/changelogs/unreleased/26585-remove-readme-view-caching.yml
new file mode 100644
index 00000000000..6aefae982bf
--- /dev/null
+++ b/changelogs/unreleased/26585-remove-readme-view-caching.yml
@@ -0,0 +1,4 @@
+---
+title: 'Remove view fragment caching for project READMEs'
+merge_request: 8838
+author:
diff --git a/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml b/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml
new file mode 100644
index 00000000000..42fd71ccd5f
--- /dev/null
+++ b/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admins to sudo to blocked users via the API
+merge_request: 10842
+author:
diff --git a/changelogs/unreleased/30466-click-x-to-remove-filter.yml b/changelogs/unreleased/30466-click-x-to-remove-filter.yml
new file mode 100644
index 00000000000..2cf08e84ed1
--- /dev/null
+++ b/changelogs/unreleased/30466-click-x-to-remove-filter.yml
@@ -0,0 +1,4 @@
+---
+title: Add button to delete filters from filtered search bar
+merge_request:
+author:
diff --git a/changelogs/unreleased/30645-show-pipeline-events-description.yml b/changelogs/unreleased/30645-show-pipeline-events-description.yml
new file mode 100644
index 00000000000..fb75dde1d86
--- /dev/null
+++ b/changelogs/unreleased/30645-show-pipeline-events-description.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline events description for Slack and Mattermost integration
+merge_request: 10908
+author:
diff --git a/changelogs/unreleased/30973-fix-network-graph-ordering.yml b/changelogs/unreleased/30973-fix-network-graph-ordering.yml
new file mode 100644
index 00000000000..420ec107842
--- /dev/null
+++ b/changelogs/unreleased/30973-fix-network-graph-ordering.yml
@@ -0,0 +1,4 @@
+---
+title: Fix ordering of commits in the network graph
+merge_request: 10936
+author:
diff --git a/changelogs/unreleased/31138-improve-test-settings-for-services-in-empty-projects.yml b/changelogs/unreleased/31138-improve-test-settings-for-services-in-empty-projects.yml
new file mode 100644
index 00000000000..cb1de425d66
--- /dev/null
+++ b/changelogs/unreleased/31138-improve-test-settings-for-services-in-empty-projects.yml
@@ -0,0 +1,4 @@
+---
+title: Improves test settings for chat notification services for empty projects
+merge_request: 10886
+author:
diff --git a/changelogs/unreleased/31292-milestone-sidebar-display-incorect-number-of-mr-when-minimized.yml b/changelogs/unreleased/31292-milestone-sidebar-display-incorect-number-of-mr-when-minimized.yml
new file mode 100644
index 00000000000..dee831c668b
--- /dev/null
+++ b/changelogs/unreleased/31292-milestone-sidebar-display-incorect-number-of-mr-when-minimized.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed milestone sidebar showing incorrect number of MRs when collapsed
+merge_request: 10933
+author:
diff --git a/changelogs/unreleased/add-username-to-activity-feed.yml b/changelogs/unreleased/add-username-to-activity-feed.yml
new file mode 100644
index 00000000000..f4c216a3954
--- /dev/null
+++ b/changelogs/unreleased/add-username-to-activity-feed.yml
@@ -0,0 +1,4 @@
+---
+title: Add username to activity atom feed
+merge_request: 10802
+author: winniehell
diff --git a/changelogs/unreleased/dm-sidekiq-5.yml b/changelogs/unreleased/dm-sidekiq-5.yml
new file mode 100644
index 00000000000..69c94b18929
--- /dev/null
+++ b/changelogs/unreleased/dm-sidekiq-5.yml
@@ -0,0 +1,4 @@
+---
+title: Bump Sidekiq to 5.0.0
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-notify-post-receive.yml b/changelogs/unreleased/fix-notify-post-receive.yml
new file mode 100644
index 00000000000..6b68396d5c5
--- /dev/null
+++ b/changelogs/unreleased/fix-notify-post-receive.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed wrong method call on notify_post_receive
+merge_request:
+author: Luigi Leoni
diff --git a/changelogs/unreleased/fix_emoji_parser.yml b/changelogs/unreleased/fix_emoji_parser.yml
new file mode 100644
index 00000000000..2b1fffe2457
--- /dev/null
+++ b/changelogs/unreleased/fix_emoji_parser.yml
@@ -0,0 +1,4 @@
+---
+title: Fix rendering emoji inside a string
+merge_request: 10647
+author: blackst0ne
diff --git a/changelogs/unreleased/gl-version-backup-file.yml b/changelogs/unreleased/gl-version-backup-file.yml
new file mode 100644
index 00000000000..9b5abd58ae7
--- /dev/null
+++ b/changelogs/unreleased/gl-version-backup-file.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor backup/restore docs
+merge_request:
+author:
diff --git a/changelogs/unreleased/replace_header_mr_icon.yml b/changelogs/unreleased/replace_header_mr_icon.yml
new file mode 100644
index 00000000000..2ef6500f88a
--- /dev/null
+++ b/changelogs/unreleased/replace_header_mr_icon.yml
@@ -0,0 +1,4 @@
+---
+title: Replace header merge request icon
+merge_request: 10932
+author: blackst0ne
diff --git a/changelogs/unreleased/tc-make-user-master-project-by-admin.yml b/changelogs/unreleased/tc-make-user-master-project-by-admin.yml
new file mode 100644
index 00000000000..459d6178bdd
--- /dev/null
+++ b/changelogs/unreleased/tc-make-user-master-project-by-admin.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure namespace owner is Master of project upon creation
+merge_request: 10910
+author:
diff --git a/changelogs/unreleased/zj-dockerfiles.yml b/changelogs/unreleased/zj-dockerfiles.yml
new file mode 100644
index 00000000000..40cb7dcfb76
--- /dev/null
+++ b/changelogs/unreleased/zj-dockerfiles.yml
@@ -0,0 +1,4 @@
+---
+title: Dockerfiles templates are imported from gitlab.com/gitlab-org/Dockerfile
+merge_request: 10663
+author:
diff --git a/db/migrate/20170421102337_remove_nil_type_services.rb b/db/migrate/20170421102337_remove_nil_type_services.rb
new file mode 100644
index 00000000000..b835b9c6ed9
--- /dev/null
+++ b/db/migrate/20170421102337_remove_nil_type_services.rb
@@ -0,0 +1,12 @@
+class RemoveNilTypeServices < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ execute <<-SQL
+ DELETE FROM services WHERE type IS NULL OR type = '';
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb b/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb
new file mode 100644
index 00000000000..58ad2c64075
--- /dev/null
+++ b/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb
@@ -0,0 +1,10 @@
+class FillMissingUuidOnApplicationSettings < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ execute("UPDATE application_settings SET uuid = #{quote(SecureRandom.uuid)} WHERE uuid is NULL")
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ff00951d5f6..49d7c996661 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170424142900) do
+ActiveRecord::Schema.define(version: 20170426175636) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index 3245988fc14..d9ca74ca1a3 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -18,7 +18,8 @@ you need to use with GitLab.
## GitLab Pages Ports
-If you're using GitLab Pages you will need some additional port configurations.
+If you're using GitLab Pages with custom domain support you will need some
+additional port configurations.
GitLab Pages requires a separate virtual IP address. Configure DNS to point the
`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new virtual IP address. See the
[GitLab Pages documentation][gitlab-pages] for more information.
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index bf1aa6b9ac5..c5125dc6d5a 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -7,21 +7,20 @@ supported natively in NFS version 4. NFSv3 also supports locking as long as
Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
specifically test NFSv3.
-**no_root_squash**: NFS normally changes the `root` user to `nobody`. This is
-a good security measure when NFS shares will be accessed by many different
-users. However, in this case only GitLab will use the NFS share so it
-is safe. GitLab requires the `no_root_squash` setting because we need to
-manage file permissions automatically. Without the setting you will receive
-errors when the Omnibus package tries to alter permissions. Note that GitLab
-and other bundled components do **not** run as `root` but as non-privileged
-users. The requirement for `no_root_squash` is to allow the Omnibus package to
-set ownership and permissions on files, as needed.
-
### Recommended options
When you define your NFS exports, we recommend you also add the following
options:
+- `no_root_squash` - NFS normally changes the `root` user to `nobody`. This is
+ a good security measure when NFS shares will be accessed by many different
+ users. However, in this case only GitLab will use the NFS share so it
+ is safe. GitLab recommends the `no_root_squash` setting because we need to
+ manage file permissions automatically. Without the setting you may receive
+ errors when the Omnibus package tries to alter permissions. Note that GitLab
+ and other bundled components do **not** run as `root` but as non-privileged
+ users. The recommendation for `no_root_squash` is to allow the Omnibus package
+ to set ownership and permissions on files, as needed.
- `sync` - Force synchronous behavior. Default is asynchronous and under certain
circumstances it could lead to data loss if a failure occurs before data has
synced.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index e680a560888..5be6053b76e 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -1,41 +1,52 @@
-# Backup restore
+# Backing up and restoring GitLab
![backup banner](backup_hrz.png)
An application data backup creates an archive file that contains the database,
all repositories and all attachments.
-This archive will be saved in `backup_path`, which is specified in the
-`config/gitlab.yml` file.
-The filename will be `[TIMESTAMP]_gitlab_backup.tar`, where `TIMESTAMP`
-identifies the time at which each backup was created.
-
-> In GitLab 8.15 we changed the timestamp format from `EPOCH` (`1393513186`)
-> to `EPOCH_YYYY_MM_DD` (`1393513186_2014_02_27`)
-You can only restore a backup to exactly the same version of GitLab on which it
-was created. The best way to migrate your repositories from one server to
+You can only restore a backup to **exactly the same version** of GitLab on which
+it was created. The best way to migrate your repositories from one server to
another is through backup restore.
-To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json`
-(for omnibus packages) or `/home/git/gitlab/.secret` (for installations
-from source). This file contains the database encryption key,
-[CI secret variables](../ci/variables/README.md#secret-variables), and
-secret variables used for [two-factor authentication](../security/two_factor_authentication.md).
-If you fail to restore this encryption key file along with the application data
-backup, users with two-factor authentication enabled and GitLab Runners will
-lose access to your GitLab server.
+## Backup
+
+GitLab provides a simple command line interface to backup your whole installation,
+and is flexible enough to fit your needs.
-## Create a backup of the GitLab system
+### Backup timestamp
+
+>**Note:**
+In GitLab 9.2 the timestamp format was changed from `EPOCH_YYYY_MM_DD` to
+`EPOCH_YYYY_MM_DD_GitLab version`, for example `1493107454_2017_04_25`
+would become `1493107454_2017_04_25_9.1.0`.
+
+The backup archive will be saved in `backup_path`, which is specified in the
+`config/gitlab.yml` file.
+The filename will be `[TIMESTAMP]_gitlab_backup.tar`, where `TIMESTAMP`
+identifies the time at which each backup was created, plus the GitLab version.
+The timestamp is needed if you need to restore GitLab and multiple backups are
+available.
+
+For example, if the backup name is `1493107454_2017_04_25_9.1.0_gitlab_backup.tar`,
+then the timestamp is `1493107454_2017_04_25_9.1.0`.
+
+### Creating a backup of the GitLab system
Use this command if you've installed GitLab with the Omnibus package:
+
```
sudo gitlab-rake gitlab:backup:create
```
+
Use this if you've installed GitLab from source:
+
```
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
+
If you are running GitLab within a Docker container, you can run the backup from the host:
+
```
docker exec -t <container name> gitlab-rake gitlab:backup:create
```
@@ -69,9 +80,9 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING]
```
-## Backup Strategy Option
+### Backup strategy option
-> **Note:** Introduced as an option in 8.17
+> **Note:** Introduced as an option in GitLab 8.17.
The default backup strategy is to essentially stream data from the respective
data locations to the backup using the Linux command `tar` and `gzip`. This works
@@ -91,7 +102,7 @@ To use the `copy` strategy instead of the default streaming strategy, specify
`STRATEGY=copy` in the Rake task command. For example,
`sudo gitlab-rake gitlab:backup:create STRATEGY=copy`.
-## Exclude specific directories from the backup
+### Excluding specific directories from the backup
You can choose what should be backed up by adding the environment variable `SKIP`.
The available options are:
@@ -115,7 +126,7 @@ sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production
```
-## Upload backups to remote (cloud) storage
+### Uploading backups to a remote (cloud) storage
Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
It uses the [Fog library](http://fog.io/) to perform the upload.
@@ -259,7 +270,7 @@ For installations from source:
remote_directory: 'gitlab_backups'
```
-## Backup archive permissions
+### Backup archive permissions
The backup archives created by GitLab (`1393513186_2014_02_27_gitlab_backup.tar`)
will have owner/group git:git and 0600 permissions by default.
@@ -277,11 +288,11 @@ gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives wo
archive_permissions: 0644 # Makes the backup archives world-readable
```
-## Storing configuration files
+### Storing configuration files
Please be informed that a backup does not store your configuration
-files. One reason for this is that your database contains encrypted
-information for two-factor authentication. Storing encrypted
+files. One reason for this is that your database contains encrypted
+information for two-factor authentication. Storing encrypted
information along with its key in the same place defeats the purpose
of using encryption in the first place!
@@ -294,11 +305,74 @@ At the very **minimum** you should backup `/etc/gitlab/gitlab.rb` and
`/home/git/gitlab/config/secrets.yml` (source) to preserve your database
encryption key.
-## Restore a previously created backup
+### Configuring cron to make daily backups
+
+>**Note:**
+The following cron jobs do not [backup your GitLab configuration files](#storing-configuration-files)
+or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
+**For Omnibus installations**
+
+To schedule a cron job that backs up your repositories and GitLab metadata, use the root user:
+
+```
+sudo su -
+crontab -e
+```
+
+There, add the following line to schedule the backup for everyday at 2 AM:
+
+```
+0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1
+```
+
+You may also want to set a limited lifetime for backups to prevent regular
+backups using all your disk space. To do this add the following lines to
+`/etc/gitlab/gitlab.rb` and reconfigure:
-You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+```
+# limit backup lifetime to 7 days - 604800 seconds
+gitlab_rails['backup_keep_time'] = 604800
+```
-### Prerequisites
+Note that the `backup_keep_time` configuration option only manages local
+files. GitLab does not automatically prune old files stored in a third-party
+object storage (e.g., AWS S3) because the user may not have permission to list
+and delete files. We recommend that you configure the appropriate retention
+policy for your object storage. For example, you can configure [the S3 backup
+policy as described here](http://stackoverflow.com/questions/37553070/gitlab-omnibus-delete-backup-from-amazon-s3).
+
+**For installation from source**
+
+```
+cd /home/git/gitlab
+sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
+sudo -u git crontab -e # Edit the crontab for the git user
+```
+
+Add the following lines at the bottom:
+
+```
+# Create a full backup of the GitLab repositories and SQL database every day at 4am
+0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
+```
+
+The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
+This is recommended to reduce cron spam.
+
+## Restore
+
+GitLab provides a simple command line interface to backup your whole installation,
+and is flexible enough to fit your needs.
+
+The [restore prerequisites section](#restore-prerequisites) includes crucial
+information. Make sure to read and test the whole restore process at least once
+before attempting to perform it in a production environment.
+
+You can only restore a backup to **exactly the same version** of GitLab that
+you created it on, for example 9.1.0.
+
+### Restore prerequisites
You need to have a working GitLab installation before you can perform
a restore. This is mainly because the system user performing the
@@ -307,13 +381,23 @@ the SQL database it needs to import data into ('gitlabhq_production').
All existing data will be either erased (SQL) or moved to a separate
directory (repositories, uploads).
-If some or all of your GitLab users are using two-factor authentication (2FA)
-then you must also make sure to restore `/etc/gitlab/gitlab.rb` and
-`/etc/gitlab/gitlab-secrets.json` (Omnibus), or
-`/home/git/gitlab/config/secrets.yml` (installations from source). Note that you
-need to run `gitlab-ctl reconfigure` after changing `gitlab-secrets.json`.
+To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json`
+(for Omnibus packages) or `/home/git/gitlab/.secret` (for installations
+from source). This file contains the database encryption key,
+[CI secret variables](../ci/variables/README.md#secret-variables), and
+secret variables used for [two-factor authentication](../user/profile/account/two_factor_authentication.md).
+If you fail to restore this encryption key file along with the application data
+backup, users with two-factor authentication enabled and GitLab Runners will
+lose access to your GitLab server.
+
+Depending on your case, you might want to run the restore command with one or
+more of the following options:
+
+- `BACKUP=timestamp_of_backup` - Required if more than one backup exists.
+ Read what the [backup timestamp is about](#backup-timestamp).
+- `force=yes` - Do not ask if the authorized_keys file should get regenerated.
-### Installation from source
+### Restore for installation from source
```
# Stop processes that are connected to the database
@@ -322,13 +406,6 @@ sudo service gitlab stop
bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
-Options:
-
-```
-BACKUP=timestamp_of_backup (required if more than one backup exists)
-force=yes (do not ask if the authorized_keys file should get regenerated)
-```
-
Example output:
```
@@ -360,13 +437,13 @@ Restoring repositories:
Deleting tmp directories...[DONE]
```
-### Omnibus installations
+### Restore for Omnibus installations
This procedure assumes that:
-- You have installed the exact same version of GitLab Omnibus with which the
- backup was created
-- You have run `sudo gitlab-ctl reconfigure` at least once
+- You have installed the **exact same version** of GitLab Omnibus with which the
+ backup was created.
+- You have run `sudo gitlab-ctl reconfigure` at least once.
- GitLab is running. If not, start it using `sudo gitlab-ctl start`.
First make sure your backup tar file is in the backup directory described in the
@@ -374,7 +451,7 @@ First make sure your backup tar file is in the backup directory described in the
`/var/opt/gitlab/backups`.
```shell
-sudo cp 1393513186_2014_02_27_gitlab_backup.tar /var/opt/gitlab/backups/
+sudo cp 1493107454_2017_04_25_9.1.0_gitlab_backup.tar /var/opt/gitlab/backups/
```
Stop the processes that are connected to the database. Leave the rest of GitLab
@@ -392,7 +469,7 @@ restore:
```shell
# This command will overwrite the contents of your GitLab database!
-sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186_2014_02_27
+sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2017_04_25_9.1.0
```
Restart and check GitLab:
@@ -404,59 +481,7 @@ sudo gitlab-rake gitlab:check SANITIZE=true
If there is a GitLab version mismatch between your backup tar file and the installed
version of GitLab, the restore command will abort with an error. Install the
-[correct GitLab version](https://about.gitlab.com/downloads/archives/) and try again.
-
-## Configure cron to make daily backups
-
-### For installation from source:
-```
-cd /home/git/gitlab
-sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
-sudo -u git crontab -e # Edit the crontab for the git user
-```
-
-Add the following lines at the bottom:
-
-```
-# Create a full backup of the GitLab repositories and SQL database every day at 4am
-0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
-```
-
-The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
-This is recommended to reduce cron spam.
-
-### For omnibus installations
-
-To schedule a cron job that backs up your repositories and GitLab metadata, use the root user:
-
-```
-sudo su -
-crontab -e
-```
-
-There, add the following line to schedule the backup for everyday at 2 AM:
-
-```
-0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1
-```
-
-You may also want to set a limited lifetime for backups to prevent regular
-backups using all your disk space. To do this add the following lines to
-`/etc/gitlab/gitlab.rb` and reconfigure:
-
-```
-# limit backup lifetime to 7 days - 604800 seconds
-gitlab_rails['backup_keep_time'] = 604800
-```
-
-Note that the `backup_keep_time` configuration option only manages local
-files. GitLab does not automatically prune old files stored in a third-party
-object storage (e.g. AWS S3) because the user may not have permission to list
-and delete files. We recommend that you configure the appropriate retention
-policy for your object storage. For example, you can configure [the S3 backup
-policy here as described here](http://stackoverflow.com/questions/37553070/gitlab-omnibus-delete-backup-from-amazon-s3).
-
-NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+[correct GitLab version](https://packages.gitlab.com/gitlab/) and try again.
## Alternative backup strategies
@@ -481,6 +506,19 @@ Example: LVM snapshots + rsync
If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
+## Additional notes
+
+This documentation is for GitLab Community and Enterprise Edition. We backup
+GitLab.com and make sure your data is secure, but you can't use these methods
+to export / backup your data yourself from GitLab.com.
+
+Issues are stored in the database. They can't be stored in Git itself.
+
+To migrate your repositories from one server to another with an up-to-date version of
+GitLab, you can use the [import rake task](import.md) to do a mass import of the
+repository. Note that if you do an import rake task, rather than a backup restore, you
+will have all your repositories, but not any other data.
+
## Troubleshooting
### Restoring database backup using omnibus packages outputs warnings
@@ -490,7 +528,6 @@ If you are using backup restore procedures you might encounter the following war
psql:/var/opt/gitlab/backups/db/database.sql:22: ERROR: must be owner of extension plpgsql
psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING: no privileges could be revoked for "public" (two occurrences)
psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING: no privileges were granted for "public" (two occurrences)
-
```
Be advised that, backup is successfully restored in spite of these warnings.
@@ -499,14 +536,3 @@ The rake task runs this as the `gitlab` user which does not have the superuser a
Those objects have no influence on the database backup/restore but they give this annoying warning.
For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql).
-
-## Note
-This documentation is for GitLab CE.
-We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com.
-
-Issues are stored in the database. They can't be stored in Git itself.
-
-To migrate your repositories from one server to another with an up-to-date version of
-GitLab, you can use the [import rake task](import.md) to do a mass import of the
-repository. Note that if you do an import rake task, rather than a backup restore, you
-will have all your repositories, but not any other data.
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index c3f3179d99e..733e70ca9bf 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -3,7 +3,7 @@
GitLab Inc. will periodically collect information about your instance in order
to perform various actions.
-All statistics are opt-in and you can always disable them from the admin panel.
+All statistics are opt-out, you can disable them from the admin panel.
## Version check
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index cad4757f287..1e28646bc97 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -51,9 +51,9 @@ service in GitLab.
## Troubleshooting
-If builds are not triggered, these are a couple of things to keep in mind.
+If builds are not triggered, ensure you entered the right GitLab IP address in
+Bamboo under 'Trigger IP addresses'.
+
+>**Note:**
+- Starting with GitLab 8.14.0, builds are triggered on push events.
-1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
- IP addresses'.
-1. Remember that GitLab only triggers builds on push events. A commit via the
- web interface will not trigger CI currently.
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index ddff3c8c1e8..86bf567fe69 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -102,7 +102,7 @@ module API
end
def authenticate!
- unauthorized! unless current_user && can?(current_user, :access_api)
+ unauthorized! unless current_user && can?(initial_current_user, :access_api)
end
def authenticate_non_get!
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 5b48ee8665f..ebed26dd178 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -140,7 +140,7 @@ module API
begin
Gitlab::GitalyClient::Notifications.new(project.repository).post_receive
rescue GRPC::Unavailable => e
- render_api_error(e, 500)
+ render_api_error!(e, 500)
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 8d64c82272a..330cd963626 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -15,7 +15,7 @@ module Backup
s[:gitlab_version] = Gitlab::VERSION
s[:tar_version] = tar_version
s[:skipped] = ENV["SKIP"]
- tar_file = "#{s[:backup_created_at].strftime('%s_%Y_%m_%d')}#{FILE_NAME_SUFFIX}"
+ tar_file = "#{s[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{s[:gitlab_version]}#{FILE_NAME_SUFFIX}"
Dir.chdir(backup_path) do
File.open("#{backup_path}/backup_information.yml", "w+") do |file|
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index d6138816e70..6255a611dbe 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -53,7 +53,10 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
- @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+ @emoji_pattern ||=
+ /(?<=[^[:alnum:]:]|\n|^)
+ :(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):
+ (?=[^[:alnum:]:]|$)/x
end
# Build a regexp that matches all valid unicode emojis names.
diff --git a/lib/github/import.rb b/lib/github/import.rb
index 3e9162ffb9d..d49761fd6c6 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -171,24 +171,28 @@ module Github
begin
restore_branches(pull_request)
- author_id = user_id(pull_request.author, project.creator_id)
- merge_request.iid = pull_request.iid
- merge_request.title = pull_request.title
- merge_request.description = format_description(pull_request.description, pull_request.author)
- merge_request.source_project = pull_request.source_project
- merge_request.source_branch = pull_request.source_branch_name
- merge_request.source_branch_sha = pull_request.source_branch_sha
- merge_request.target_project = pull_request.target_project
- merge_request.target_branch = pull_request.target_branch_name
- merge_request.target_branch_sha = pull_request.target_branch_sha
- merge_request.state = pull_request.state
- merge_request.milestone_id = milestone_id(pull_request.milestone)
- merge_request.author_id = author_id
- merge_request.assignee_id = user_id(pull_request.assignee)
- merge_request.created_at = pull_request.created_at
- merge_request.updated_at = pull_request.updated_at
- merge_request.save!(validate: false)
+ author_id = user_id(pull_request.author, project.creator_id)
+ description = format_description(pull_request.description, pull_request.author)
+
+ merge_request.attributes = {
+ iid: pull_request.iid,
+ title: pull_request.title,
+ description: description,
+ source_project: pull_request.source_project,
+ source_branch: pull_request.source_branch_name,
+ source_branch_sha: pull_request.source_branch_sha,
+ target_project: pull_request.target_project,
+ target_branch: pull_request.target_branch_name,
+ target_branch_sha: pull_request.target_branch_sha,
+ state: pull_request.state,
+ milestone_id: milestone_id(pull_request.milestone),
+ author_id: author_id,
+ assignee_id: user_id(pull_request.assignee),
+ created_at: pull_request.created_at,
+ updated_at: pull_request.updated_at
+ }
+ merge_request.save!(validate: false)
merge_request.merge_request_diffs.create
# Fetch review comments
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index d575367d81a..fba80c7132e 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -14,28 +14,16 @@ module Gitlab
# Public: Converts the provided Asciidoc markup into HTML.
#
# input - the source text in Asciidoc format
- # context - a Hash with the template context:
- # :commit
- # :project
- # :project_wiki
- # :requested_path
- # :ref
- # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter
#
- def self.render(input, context, asciidoc_opts = {})
- asciidoc_opts.reverse_merge!(
- safe: :secure,
- backend: :gitlab_html5,
- attributes: []
- )
- asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS)
+ def self.render(input)
+ asciidoc_opts = { safe: :secure,
+ backend: :gitlab_html5,
+ attributes: DEFAULT_ADOC_ATTRS }
plantuml_setup
html = ::Asciidoctor.convert(input, asciidoc_opts)
- html = Banzai.post_process(html, context)
-
filter = Banzai::Filter::SanitizationFilter.new(html)
html = filter.call.to_s
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index d76aa38f741..1ff34553f0a 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -41,7 +41,7 @@ module Gitlab
type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
# Hash to be passed as post_receive_data
- data = {
+ {
object_kind: type,
event_name: type,
before: oldrev,
@@ -61,16 +61,15 @@ module Gitlab
repository: project.hook_attrs.slice(:name, :url, :description, :homepage,
:git_http_url, :git_ssh_url, :visibility_level)
}
-
- data
end
# This method provide a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
- commits = project.repository.commits(project.default_branch, limit: 3)
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
- build(project, user, commits.last.id, commits.first.id, ref, commits)
+ commits = project.repository.commits(project.default_branch.to_s, limit: 3) rescue []
+
+ build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end
def checkout_sha(repository, newrev, ref)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d7dac9f6149..452dba7971d 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -494,7 +494,9 @@ module Gitlab
# :contains is the commit contained by the refs from which to begin (SHA1 or name)
# :max_count is the maximum number of commits to fetch
# :skip is the number of commits to skip
- # :order is the commits order and allowed value is :date(default) or :topo
+ # :order is the commits order and allowed value is :none (default), :date, or :topo
+ # commit ordering types are documented here:
+ # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
#
def find_commits(options = {})
actual_options = options.dup
@@ -522,11 +524,8 @@ module Gitlab
end
end
- if actual_options[:order] == :topo
- walker.sorting(Rugged::SORT_TOPO)
- else
- walker.sorting(Rugged::SORT_NONE)
- end
+ sort_type = rugged_sort_type(actual_options[:order])
+ walker.sorting(sort_type)
commits = []
offset = actual_options[:skip]
@@ -1273,6 +1272,18 @@ module Gitlab
def gitaly_ref_client
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
end
+
+ # Returns the `Rugged` sorting type constant for a given
+ # sort type key. Valid keys are `:none`, `:topo`, and `:date`
+ def rugged_sort_type(key)
+ @rugged_sort_types ||= {
+ none: Rugged::SORT_NONE,
+ topo: Rugged::SORT_TOPO,
+ date: Rugged::SORT_DATE
+ }
+
+ @rugged_sort_types.fetch(key, Rugged::SORT_NONE)
+ end
end
end
end
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index e67acf28c94..c2adc9aa10b 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -4,19 +4,11 @@ module Gitlab
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
- # context - a Hash with the template context:
- # :commit
- # :project
- # :project_wiki
- # :requested_path
- # :ref
#
- def self.render(file_name, input, context)
+ def self.render(file_name, input)
html = GitHub::Markup.render(file_name, input).
force_encoding(input.encoding)
- html = Banzai.post_process(html, context)
-
filter = Banzai::Filter::SanitizationFilter.new(html)
html = filter.call.to_s
diff --git a/lib/gitlab/template/dockerfile_template.rb b/lib/gitlab/template/dockerfile_template.rb
index d5d3e045a42..20b054b0bd8 100644
--- a/lib/gitlab/template/dockerfile_template.rb
+++ b/lib/gitlab/template/dockerfile_template.rb
@@ -8,7 +8,7 @@ module Gitlab
class << self
def extension
- 'Dockerfile'
+ '.Dockerfile'
end
def categories
@@ -18,7 +18,7 @@ module Gitlab
end
def base_dir
- Rails.root.join('vendor/dockerfile')
+ Rails.root.join('vendor/Dockerfile')
end
def finder(project = nil)
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index cb2adc81c9d..1b04e1350ed 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -5,7 +5,7 @@ namespace :gitlab do
end
def update(template)
- sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1]
+ sub_dir = template.repo_url.match(/([A-Za-z-]+)\.git\z/)[1]
dir = File.join(vendor_directory, sub_dir)
unless clone_repository(template.repo_url, dir)
@@ -45,7 +45,11 @@ namespace :gitlab do
Template.new(
"https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
/(\.{1,2}|LICENSE|CONTRIBUTING.md|Pages|autodeploy|\.gitlab-ci.yml)\z/
- )
+ ),
+ Template.new(
+ "https://gitlab.com/gitlab-org/Dockerfile.git",
+ /(\.{1,2}|LICENSE|CONTRIBUTING.md|\.Dockerfile)\z/
+ ),
].freeze
def vendor_directory
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 16365642a34..2d892f4a2b7 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -8,6 +8,7 @@ describe Projects::ServicesController do
before do
sign_in(user)
project.team << [user, :master]
+
controller.instance_variable_set(:@project, project)
controller.instance_variable_set(:@service, service)
end
@@ -18,20 +19,60 @@ describe Projects::ServicesController do
end
describe "#test" do
+ context 'when can_test? returns false' do
+ it 'renders 404' do
+ allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
+
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
context 'success' do
+ context 'with empty project' do
+ let(:project) { create(:empty_project) }
+
+ context 'with chat notification service' do
+ let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
+
+ it 'redirects and show success message' do
+ allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
+
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+
+ expect(response).to redirect_to(root_path)
+ expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ end
+ end
+
+ it 'redirects and show success message' do
+ expect(service).to receive(:test).and_return(success: true, result: 'done')
+
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+
+ expect(response).to redirect_to(root_path)
+ expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ end
+ end
+
it "redirects and show success message" do
- expect(service).to receive(:test).and_return({ success: true, result: 'done' })
+ expect(service).to receive(:test).and_return(success: true, result: 'done')
+
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
+
+ expect(response).to redirect_to(root_path)
expect(flash[:notice]).to eq('We sent a request to the provided URL')
end
end
context 'failure' do
it "redirects and show failure message" do
- expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
+ expect(service).to receive(:test).and_return(success: false, result: 'Bad test')
+
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
+
+ expect(response).to redirect_to(root_path)
expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 88f6c265505..62aa71ae8d8 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,6 +1,19 @@
FactoryGirl.define do
factory :service do
project factory: :empty_project
+ type 'Service'
+ end
+
+ factory :custom_issue_tracker_service, class: CustomIssueTrackerService do
+ project factory: :empty_project
+ type 'CustomIssueTrackerService'
+ category 'issue_tracker'
+ active true
+ properties(
+ project_url: 'https://project.url.com',
+ issues_url: 'https://issues.url.com',
+ new_issue_url: 'https://newissue.url.com'
+ )
end
factory :kubernetes_service do
diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb
new file mode 100644
index 00000000000..dd14ffdb2ce
--- /dev/null
+++ b/spec/features/admin/admin_cohorts_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+feature 'Admin cohorts page', feature: true do
+ before do
+ login_as :admin
+ end
+
+ scenario 'See users count per month' do
+ 2.times { create(:user) }
+
+ visit admin_cohorts_path
+
+ expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0")
+ end
+end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 64a6c70061b..344e31e5ef5 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'Copy as GFM', feature: true, js: true do
- include GitlabMarkdownHelper
+ include MarkupHelper
include RepoHelpers
include ActionView::Helpers::JavaScriptHelper
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 81ee0e2e4f6..c824aa6a414 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -12,7 +12,7 @@ describe 'Filter issues', js: true, feature: true do
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
let!(:bug_label) { create(:label, project: project, title: 'bug') }
- let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') }
+ let!(:caps_sensitive_label) { create(:label, project: project, title: 'CaPs') }
let!(:milestone) { create(:milestone, title: "8", project: project, start_date: 2.days.ago) }
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index b571f6bd861..ad29911248f 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -45,6 +45,33 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
end
+ it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
+ note = find('#note_note')
+
+ # Number.
+ page.within '.timeline-content-form' do
+ note.native.send_keys('7:')
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
+
+ # ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('w:')
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
+
+ # Non-ASCII letter.
+ page.within '.timeline-content-form' do
+ note.set('')
+ note.native.send_keys('Ё:')
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
+ end
+
it 'selects the first item for assignee dropdowns' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 894df13a2dc..ba930de937d 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -26,7 +26,7 @@ require 'erb'
describe 'GitLab Markdown', feature: true do
include Capybara::Node::Matchers
- include GitlabMarkdownHelper
+ include MarkupHelper
include MarkdownMatchers
# Sometimes it can be useful to see the parsed output of the Markdown document
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index a7cc98a2059..548131c7cd4 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -1,11 +1,14 @@
require 'spec_helper'
+require 'fileutils'
feature 'User wants to add a Dockerfile file', feature: true do
before do
user = create(:user)
project = create(:project)
project.team << [user, :master]
+
login_as user
+
visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile')
end
@@ -15,11 +18,14 @@ feature 'User wants to add a Dockerfile file', feature: true do
scenario 'user can pick a Dockerfile file from the dropdown', js: true do
find('.js-dockerfile-selector').click
+
wait_for_ajax
+
within '.dockerfile-selector' do
find('.dropdown-input-field').set('HTTPd')
find('.dropdown-content li', text: 'HTTPd').click
end
+
wait_for_ajax
expect(page).to have_css('.dockerfile-selector .dropdown-toggle-text', text: 'HTTPd')
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index dab78fd3571..5e19907eef9 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -63,4 +63,28 @@ feature 'Project milestone', :feature do
expect(page).not_to have_content('Assign some issues to this milestone.')
end
end
+
+ context 'when project has an issue' do
+ before do
+ create(:issue, project: project, milestone: milestone)
+
+ visit namespace_project_milestone_path(project.namespace, project, milestone)
+ end
+
+ describe 'the collapsed sidebar' do
+ before do
+ find('.milestone-sidebar .gutter-toggle').click
+ end
+
+ it 'shows the total MR and issue counts' do
+ find('.milestone-sidebar .block', match: :first)
+ blocks = all('.milestone-sidebar .block')
+
+ aggregate_failures 'MR and issue blocks' do
+ expect(blocks[3]).to have_content 1
+ expect(blocks[4]).to have_content 0
+ end
+ end
+ end
+ end
end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 0cdbc32431d..51a3e91d201 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -116,7 +116,7 @@ Linking to a file relative to this project's repository should work.
Because life would be :zzz: without Emoji, right? :rocket:
-Get ready for the Emoji :bomb:: :+1::-1::ok_hand::wave::v::raised_hand::muscle:
+Get ready for the Emoji :bomb: : :+1: :-1: :ok_hand: :wave: :v: :raised_hand: :muscle:
### TableOfContentsFilter
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 5c07ea8a872..01bdf01ad22 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -239,33 +239,6 @@ describe ApplicationHelper do
end
end
- describe 'render_markup' do
- let(:content) { 'Noël' }
- let(:user) { create(:user) }
- before do
- allow(helper).to receive(:current_user).and_return(user)
- end
-
- it 'preserves encoding' do
- expect(content.encoding.name).to eq('UTF-8')
- expect(helper.render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
- end
-
- it "delegates to #markdown when file name corresponds to Markdown" do
- expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
- expect(helper).to receive(:markdown).and_return('NOEL')
-
- expect(helper.render_markup('foo.md', content)).to eq('NOEL')
- end
-
- it "delegates to #asciidoc when file name corresponds to AsciiDoc" do
- expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
- expect(helper).to receive(:asciidoc).and_return('NOEL')
-
- expect(helper.render_markup('foo.adoc', content)).to eq('NOEL')
- end
- end
-
describe '#active_when' do
it { expect(helper.active_when(true)).to eq('active') }
it { expect(helper.active_when(false)).to eq(nil) }
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 6cf3f86680a..c10f4b09b5b 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
-describe GitlabMarkdownHelper do
- include ApplicationHelper
-
+describe MarkupHelper do
let!(:project) { create(:project, :repository) }
let(:user) { create(:user, username: 'gfm') }
@@ -111,9 +109,9 @@ describe GitlabMarkdownHelper do
end
it 'replaces commit message with emoji to link' do
- actual = link_to_gfm(':book:Book', '/foo')
+ actual = link_to_gfm(':book: Book', '/foo')
expect(actual).
- to eq '<gl-emoji data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo">Book</a>'
+ to eq '<gl-emoji data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
end
end
@@ -128,7 +126,7 @@ describe GitlabMarkdownHelper do
it "uses Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
- expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki, page_slug: "nested/page")
+ expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page")
helper.render_wiki_content(@wiki)
end
@@ -136,7 +134,7 @@ describe GitlabMarkdownHelper do
it "uses Asciidoctor for asciidoc files" do
allow(@wiki).to receive(:format).and_return(:asciidoc)
- expect(helper).to receive(:asciidoc).with('wiki content')
+ expect(helper).to receive(:asciidoc_unsafe).with('wiki content')
helper.render_wiki_content(@wiki)
end
@@ -151,6 +149,29 @@ describe GitlabMarkdownHelper do
end
end
+ describe 'markup' do
+ let(:content) { 'Noël' }
+
+ it 'preserves encoding' do
+ expect(content.encoding.name).to eq('UTF-8')
+ expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8')
+ end
+
+ it "delegates to #markdown_unsafe when file name corresponds to Markdown" do
+ expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
+ expect(helper).to receive(:markdown_unsafe).and_return('NOEL')
+
+ expect(helper.markup('foo.md', content)).to eq('NOEL')
+ end
+
+ it "delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc" do
+ expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
+ expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL')
+
+ expect(helper.markup('foo.adoc', content)).to eq('NOEL')
+ end
+ end
+
describe '#first_line_in_markdown' do
it 'truncates Markdown properly' do
text = "@#{user.username}, can you look at this?\nHello world\n"
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 9762688af1a..1c54cc3054c 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -1,15 +1,18 @@
import Vue from 'vue';
import '~/flash';
-import EnvironmentsComponent from '~/environments/components/environment';
+import environmentsComponent from '~/environments/components/environment.vue';
import { environment, folder } from './mock_data';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
+ let EnvironmentsComponent;
let component;
beforeEach(() => {
loadFixtures('static/environments/environments.html.raw');
+
+ EnvironmentsComponent = Vue.extend(environmentsComponent);
});
describe('successfull request', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 72f3db29a66..350078ad5f5 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,13 +1,15 @@
import Vue from 'vue';
import '~/flash';
-import EnvironmentsFolderViewComponent from '~/environments/folder/environments_folder_view';
+import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
preloadFixtures('static/environments/environments_folder_view.html.raw');
+ let EnvironmentsFolderViewComponent;
beforeEach(() => {
loadFixtures('static/environments/environments_folder_view.html.raw');
+ EnvironmentsFolderViewComponent = Vue.extend(environmentsFolderViewComponent);
window.history.pushState({}, null, 'environments/folders/build');
});
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 6683489f63c..e747aa497c2 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -26,6 +26,10 @@ describe('Filtered Search Manager', () => {
element.dispatchEvent(event);
}
+ function getVisualTokens() {
+ return tokensContainer.querySelectorAll('.js-visual-token');
+ }
+
beforeEach(() => {
setFixtures(`
<div class="filtered-search-box">
@@ -170,11 +174,37 @@ describe('Filtered Search Manager', () => {
});
});
- describe('removeSelectedToken', () => {
- function getVisualTokens() {
- return tokensContainer.querySelectorAll('.js-visual-token');
- }
+ describe('removeToken', () => {
+ it('removes token even when it is already selected', () => {
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
+ );
+
+ tokensContainer.querySelector('.js-visual-token .remove-token').click();
+ expect(tokensContainer.querySelector('.js-visual-token')).toEqual(null);
+ });
+ describe('unselected token', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchManager.prototype, 'removeSelectedToken').and.callThrough();
+
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none'),
+ );
+ tokensContainer.querySelector('.js-visual-token .remove-token').click();
+ });
+
+ it('removes token when remove button is selected', () => {
+ expect(tokensContainer.querySelector('.js-visual-token')).toEqual(null);
+ });
+
+ it('calls removeSelectedToken', () => {
+ expect(manager.removeSelectedToken).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('removeSelectedTokenKeydown', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
@@ -224,6 +254,31 @@ describe('Filtered Search Manager', () => {
});
});
+ describe('removeSelectedToken', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchVisualTokens, 'removeSelectedToken').and.callThrough();
+ spyOn(gl.FilteredSearchManager.prototype, 'handleInputPlaceholder').and.callThrough();
+ spyOn(gl.FilteredSearchManager.prototype, 'toggleClearSearchButton').and.callThrough();
+ manager.removeSelectedToken();
+ });
+
+ it('calls FilteredSearchVisualTokens.removeSelectedToken', () => {
+ expect(gl.FilteredSearchVisualTokens.removeSelectedToken).toHaveBeenCalled();
+ });
+
+ it('calls handleInputPlaceholder', () => {
+ expect(manager.handleInputPlaceholder).toHaveBeenCalled();
+ });
+
+ it('calls toggleClearSearchButton', () => {
+ expect(manager.toggleClearSearchButton).toHaveBeenCalled();
+ });
+
+ it('calls update dropdown offset', () => {
+ expect(manager.dropdownManager.updateDropdownOffset).toHaveBeenCalled();
+ });
+ });
+
describe('unselects token', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index bbda1476fed..d75b9061281 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -214,8 +214,12 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything());
});
+ it('contains value container div', () => {
+ expect(tokenElement.querySelector('.value-container')).toEqual(jasmine.anything());
+ });
+
it('contains value div', () => {
- expect(tokenElement.querySelector('.value')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.value-container .value')).toEqual(jasmine.anything());
});
it('contains selectable class', () => {
@@ -225,6 +229,16 @@ describe('Filtered Search Visual Tokens', () => {
it('contains button role', () => {
expect(tokenElement.getAttribute('role')).toEqual('button');
});
+
+ describe('remove token', () => {
+ it('contains remove-token button', () => {
+ expect(tokenElement.querySelector('.value-container .remove-token')).toEqual(jasmine.anything());
+ });
+
+ it('contains fa-close icon', () => {
+ expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(jasmine.anything());
+ });
+ });
});
describe('addVisualTokenElement', () => {
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index ce83a256ddd..b8d4a93b1ab 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -10,7 +10,12 @@ class FilteredSearchSpecHelper {
li.innerHTML = `
<div class="selectable ${isSelected ? 'selected' : ''}" role="button">
<div class="name">${name}</div>
- <div class="value">${value}</div>
+ <div class="value-container">
+ <div class="value">${value}</div>
+ <div class="remove-token" role="button">
+ <i class="fa fa-close"></i>
+ </div>
+ </div>
</div>
`;
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index e504d41d4d4..481b46c3ac6 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -3,70 +3,84 @@
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import '~/flash';
-(() => {
- describe('Mini Pipeline Graph Dropdown', () => {
- preloadFixtures('static/mini_dropdown_graph.html.raw');
+describe('Mini Pipeline Graph Dropdown', () => {
+ preloadFixtures('static/mini_dropdown_graph.html.raw');
- beforeEach(() => {
- loadFixtures('static/mini_dropdown_graph.html.raw');
- });
+ beforeEach(() => {
+ loadFixtures('static/mini_dropdown_graph.html.raw');
+ });
- describe('When is initialized', () => {
- it('should initialize without errors when no options are given', () => {
- const miniPipelineGraph = new MiniPipelineGraph();
+ describe('When is initialized', () => {
+ it('should initialize without errors when no options are given', () => {
+ const miniPipelineGraph = new MiniPipelineGraph();
- expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
- });
+ expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
+ });
- it('should set the container as the given prop', () => {
- const container = '.foo';
+ it('should set the container as the given prop', () => {
+ const container = '.foo';
- const miniPipelineGraph = new MiniPipelineGraph({ container });
+ const miniPipelineGraph = new MiniPipelineGraph({ container });
- expect(miniPipelineGraph.container).toEqual(container);
- });
+ expect(miniPipelineGraph.container).toEqual(container);
});
+ });
- describe('When dropdown is clicked', () => {
- it('should call getBuildsList', () => {
- const getBuildsListSpy = spyOn(MiniPipelineGraph.prototype, 'getBuildsList').and.callFake(function () {});
+ describe('When dropdown is clicked', () => {
+ it('should call getBuildsList', () => {
+ const getBuildsListSpy = spyOn(
+ MiniPipelineGraph.prototype,
+ 'getBuildsList',
+ ).and.callFake(function () {});
- new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
- document.querySelector('.js-builds-dropdown-button').click();
+ document.querySelector('.js-builds-dropdown-button').click();
- expect(getBuildsListSpy).toHaveBeenCalled();
- });
+ expect(getBuildsListSpy).toHaveBeenCalled();
+ });
- it('should make a request to the endpoint provided in the html', () => {
- const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
+ it('should make a request to the endpoint provided in the html', () => {
+ const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
- new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
- document.querySelector('.js-builds-dropdown-button').click();
- expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
- });
+ document.querySelector('.js-builds-dropdown-button').click();
+ expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
+ });
- it('should not close when user uses cmd/ctrl + click', () => {
- spyOn($, 'ajax').and.callFake(function (params) {
- params.success({
- html: `<li>
- <a class="mini-pipeline-graph-dropdown-item" href="#">
- <span class="ci-status-icon ci-status-icon-failed"></span>
- <span class="ci-build-text">build</span>
- </a>
- <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
- </li>`,
- });
+ it('should not close when user uses cmd/ctrl + click', () => {
+ spyOn($, 'ajax').and.callFake(function (params) {
+ params.success({
+ html: `<li>
+ <a class="mini-pipeline-graph-dropdown-item" href="#">
+ <span class="ci-status-icon ci-status-icon-failed"></span>
+ <span class="ci-build-text">build</span>
+ </a>
+ <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
+ </li>`,
});
- new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+ });
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
- document.querySelector('.js-builds-dropdown-button').click();
+ document.querySelector('.js-builds-dropdown-button').click();
- document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
+ document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
- expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
- });
+ expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
});
});
-})();
+
+ it('should close the dropdown when request returns an error', (done) => {
+ spyOn($, 'ajax').and.callFake(options => options.error());
+
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+
+ document.querySelector('.js-builds-dropdown-button').click();
+
+ setTimeout(() => {
+ expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
+ done();
+ }, 0);
+ });
+});
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 66b57a82363..2f1154bd999 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -63,4 +63,19 @@ describe('Pipelines Stage', () => {
expect(minifiedComponent).toContain(expectedSVG);
});
});
+
+ describe('when request fails', () => {
+ it('closes dropdown', () => {
+ spyOn($, 'ajax').and.callFake(options => options.error());
+ const StageComponent = Vue.extend(Stage);
+
+ const component = new StageComponent({
+ propsData: { stage: { status: { icon: 'foo' } } },
+ }).$mount();
+
+ expect(
+ component.$el.classList.contains('open'),
+ ).toEqual(false);
+ });
+ });
});
diff --git a/spec/javascripts/pipelines/time_ago_spec.js b/spec/javascripts/pipelines/time_ago_spec.js
new file mode 100644
index 00000000000..24581e8c672
--- /dev/null
+++ b/spec/javascripts/pipelines/time_ago_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import timeAgo from '~/pipelines/components/time_ago';
+
+describe('Timeago component', () => {
+ let TimeAgo;
+ beforeEach(() => {
+ TimeAgo = Vue.extend(timeAgo);
+ });
+
+ describe('with duration', () => {
+ it('should render duration and timer svg', () => {
+ const component = new TimeAgo({
+ propsData: {
+ duration: 10,
+ finishedTime: '',
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.duration')).toBeDefined();
+ expect(component.$el.querySelector('.duration svg')).toBeDefined();
+ });
+ });
+
+ describe('without duration', () => {
+ it('should not render duration and timer svg', () => {
+ const component = new TimeAgo({
+ propsData: {
+ duration: 0,
+ finishedTime: '',
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.duration')).toBe(null);
+ });
+ });
+
+ describe('with finishedTime', () => {
+ it('should render time and calendar icon', () => {
+ const component = new TimeAgo({
+ propsData: {
+ duration: 0,
+ finishedTime: '2017-04-26T12:40:23.277Z',
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.finished-at')).toBeDefined();
+ expect(component.$el.querySelector('.finished-at i.fa-calendar')).toBeDefined();
+ expect(component.$el.querySelector('.finished-at time')).toBeDefined();
+ });
+ });
+
+ describe('without finishedTime', () => {
+ it('should not render time and calendar icon', () => {
+ const component = new TimeAgo({
+ propsData: {
+ duration: 0,
+ finishedTime: '',
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.finished-at')).toBe(null);
+ });
+ });
+});
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 707212e07fd..086a006c45f 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -68,9 +68,9 @@ describe Banzai::Filter::EmojiFilter, lib: true do
expect(doc.css('gl-emoji').size).to eq 1
end
- it 'matches multiple emoji in a row' do
+ it 'does not match multiple emoji in a row' do
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
- expect(doc.css('gl-emoji').size).to eq 3
+ expect(doc.css('gl-emoji').size).to eq 0
end
it 'unicode matches multiple emoji in a row' do
@@ -83,6 +83,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do
expect(doc.css('gl-emoji').size).to eq 6
end
+ it 'does not match emoji in a string' do
+ doc = filter("'2a00:a4c0:100::1'")
+
+ expect(doc.css('gl-emoji').size).to eq 0
+ end
+
it 'has a data-name attribute' do
doc = filter(':-1:')
expect(doc.css('gl-emoji').first.attr('data-name')).to eq 'thumbsdown'
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index bca57105d1d..0f47fb2fbd9 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -22,26 +22,9 @@ module Gitlab
expect(Asciidoctor).to receive(:convert)
.with(input, expected_asciidoc_opts).and_return(html)
- expect( render(input, context) ).to eql html
+ expect(render(input)).to eq(html)
end
- context "with asciidoc_opts" do
- let(:asciidoc_opts) { { safe: :safe, attributes: ['foo'] } }
-
- it "merges the options with default ones" do
- expected_asciidoc_opts = {
- safe: :safe,
- backend: :gitlab_html5,
- attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo']
- }
-
- expect(Asciidoctor).to receive(:convert)
- .with(input, expected_asciidoc_opts).and_return(html)
-
- render(input, context, asciidoc_opts)
- end
- end
-
context "XSS" do
links = {
'links' => {
@@ -60,7 +43,7 @@ module Gitlab
links.each do |name, data|
it "does not convert dangerous #{name} into HTML" do
- expect(render(data[:input], context)).to eql data[:output]
+ expect(render(data[:input])).to eq(data[:output])
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 3d6d7292b42..f88653cb1fe 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1031,6 +1031,35 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#find_commits' do
+ it 'should return a return a collection of commits' do
+ commits = repository.find_commits
+
+ expect(commits).not_to be_empty
+ expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
+ end
+
+ context 'while applying a sort order based on the `order` option' do
+ it "allows ordering topologically (no parents shown before their children)" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
+
+ repository.find_commits(order: :topo)
+ end
+
+ it "allows ordering by date" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE)
+
+ repository.find_commits(order: :date)
+ end
+
+ it "applies no sorting by default" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
+
+ repository.find_commits
+ end
+ end
+ end
+
describe '#branches with deleted branch' do
before(:each) do
ref = double()
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 7a0b0b06d4b..bfecfa28ed1 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6981,28 +6981,6 @@
],
"services": [
{
- "id": 164,
- "title": null,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:07.372Z",
- "updated_at": "2016-06-14T15:02:07.372Z",
- "active": false,
- "properties": {
-
- },
- "template": false,
- "push_events": true,
- "issues_events": true,
- "merge_requests_events": true,
- "tag_push_events": true,
- "note_events": true,
- "build_events": true,
- "category": "issue_tracker",
- "type": "CustomIssueTrackerService",
- "default": true,
- "wiki_page_events": true
- },
- {
"id": 100,
"title": "JetBrains TeamCity CI",
"project_id": 5,
@@ -7019,6 +6997,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "TeamcityService",
"category": "ci",
"default": false,
"wiki_page_events": true
@@ -7040,6 +7019,7 @@
"tag_push_events": true,
"note_events": true,
"pipeline_events": true,
+ "type": "SlackService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7061,6 +7041,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "RedmineService",
"category": "issue_tracker",
"default": false,
"wiki_page_events": true
@@ -7082,6 +7063,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "PushoverService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7103,6 +7085,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "PivotalTrackerService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7125,6 +7108,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "JiraService",
"category": "issue_tracker",
"default": false,
"wiki_page_events": true
@@ -7146,6 +7130,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "IrkerService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7167,6 +7152,7 @@
"tag_push_events": true,
"note_events": true,
"pipeline_events": true,
+ "type": "HipchatService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7188,6 +7174,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "GemnasiumService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7209,6 +7196,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "FlowdockService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7230,6 +7218,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "ExternalWikiService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7251,6 +7240,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "EmailsOnPushService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7272,6 +7262,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "DroneCiService",
"category": "ci",
"default": false,
"wiki_page_events": true
@@ -7293,6 +7284,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "CustomIssueTrackerService",
"category": "issue_tracker",
"default": false,
"wiki_page_events": true
@@ -7314,6 +7306,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "CampfireService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7335,6 +7328,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "BuildkiteService",
"category": "ci",
"default": false,
"wiki_page_events": true
@@ -7356,6 +7350,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "BambooService",
"category": "ci",
"default": false,
"wiki_page_events": true
@@ -7377,6 +7372,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "AssemblaService",
"category": "common",
"default": false,
"wiki_page_events": true
@@ -7398,6 +7394,7 @@
"tag_push_events": true,
"note_events": true,
"build_events": true,
+ "type": "AssemblaService",
"category": "common",
"default": false,
"wiki_page_events": true
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index fcc23a75ca1..06cd8ab87ed 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -60,7 +60,7 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
end
context 'original service exists' do
- let(:service_id) { Service.create(project: project).id }
+ let(:service_id) { create(:service, project: project).id }
it 'does not have the original service_id' do
expect(created_object.service_id).not_to eq(service_id)
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index 22e80ec46be..d6d53e8586c 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::OtherMarkup, lib: true do
}
links.each do |name, data|
it "does not convert dangerous #{name} into HTML" do
- expect(render(data[:file], data[:input], context)).to eql data[:output]
+ expect(render(data[:file], data[:input])).to eq(data[:output])
end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 01ca1584ed2..c2c19c62048 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -4,6 +4,7 @@ describe ApplicationSetting, models: true do
let(:setting) { ApplicationSetting.create_from_defaults }
it { expect(setting).to be_valid }
+ it { expect(setting.uuid).to be_present }
describe 'validations' do
let(:http) { 'http://example.com' }
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index 492c4e01bd8..46b36e11c23 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -9,4 +9,25 @@ describe Network::Graph, models: true do
expect(graph.notes).to eq( { note_on_commit.commit_id => 1 } )
end
+
+ describe "#commits" do
+ let(:graph) { described_class.new(project, 'refs/heads/master', project.repository.commit, nil) }
+
+ it "returns a list of commits" do
+ commits = graph.commits
+
+ expect(commits).not_to be_empty
+ expect(commits).to all( be_kind_of(Network::Commit) )
+ end
+
+ it "sorts the commits by commit date (descending)" do
+ # Remove duplicate timestamps because they make it harder to
+ # assert that the commits are sorted as expected.
+ commits = graph.commits.uniq(&:date)
+ sorted_commits = commits.sort_by(&:date).reverse
+
+ expect(commits).not_to be_empty
+ expect(commits.map(&:id)).to eq(sorted_commits.map(&:id))
+ end
+ end
end
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 592c90cda36..8fbe42248ae 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -11,10 +11,10 @@ describe ChatNotificationService, models: true do
describe '#can_test?' do
context 'with empty repository' do
- it 'returns false' do
+ it 'returns true' do
subject.project = create(:empty_project, :empty_repo)
- expect(subject.can_test?).to be false
+ expect(subject.can_test?).to be true
end
end
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index fbe6f344a98..869b25b933b 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -8,7 +8,7 @@ describe IssueTrackerService, models: true do
let(:service) { RedmineService.new(project: project, active: true) }
before do
- create(:service, project: project, active: true, category: 'issue_tracker')
+ create(:custom_issue_tracker_service, project: project)
end
context 'when service is changed manually by user' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 74d5ebc6db0..98d0641443e 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1803,9 +1803,9 @@ describe Repository, models: true do
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches).
- with(%i(readme license_blob license_key))
+ with(%i(rendered_readme license_blob license_key))
- expect(repository).to receive(:readme)
+ expect(repository).to receive(:rendered_readme)
expect(repository).to receive(:license_blob)
expect(repository).to receive(:license_key)
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 0e2f07e945f..134882648b9 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -6,44 +6,53 @@ describe Service, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ it { is_expected.to validate_presence_of(:type) }
+ end
+
describe "Test Button" do
- before do
- @service = Service.new
- end
+ describe '#can_test?' do
+ let(:service) { create(:service, project: project) }
- describe "Testable" do
- let(:project) { create(:project, :repository) }
+ context 'when repository is not empty' do
+ let(:project) { create(:project, :repository) }
- before do
- allow(@service).to receive(:project).and_return(project)
- @testable = @service.can_test?
+ it 'returns true' do
+ expect(service.can_test?).to be true
+ end
end
- describe '#can_test?' do
- it { expect(@testable).to eq(true) }
+ context 'when repository is empty' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns true' do
+ expect(service.can_test?).to be true
+ end
end
+ end
+
+ describe '#test' do
+ let(:data) { 'test' }
+ let(:service) { create(:service, project: project) }
- describe '#test' do
- let(:data) { 'test' }
+ context 'when repository is not empty' do
+ let(:project) { create(:project, :repository) }
it 'test runs execute' do
- expect(@service).to receive(:execute).with(data)
+ expect(service).to receive(:execute).with(data)
- @service.test(data)
+ service.test(data)
end
end
- end
- describe "With commits" do
- let(:project) { create(:project, :repository) }
+ context 'when repository is empty' do
+ let(:project) { create(:empty_project) }
- before do
- allow(@service).to receive(:project).and_return(project)
- @testable = @service.can_test?
- end
+ it 'test runs execute' do
+ expect(service).to receive(:execute).with(data)
- describe '#can_test?' do
- it { expect(@testable).to eq(true) }
+ service.test(data)
+ end
end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 4845ab1ae1f..06c8eb1d0b7 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -427,6 +427,7 @@ describe API::Helpers do
context 'current_user is nil' do
before do
expect_any_instance_of(self.class).to receive(:current_user).and_return(nil)
+ allow_any_instance_of(self.class).to receive(:initial_current_user).and_return(nil)
end
it 'returns a 401 response' do
@@ -435,13 +436,38 @@ describe API::Helpers do
end
context 'current_user is present' do
+ let(:user) { build(:user) }
+
before do
- expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(User.new)
+ expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user)
+ expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user)
end
it 'does not raise an error' do
expect { authenticate! }.not_to raise_error
end
end
+
+ context 'current_user is blocked' do
+ let(:user) { build(:user, :blocked) }
+
+ before do
+ expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user)
+ end
+
+ it 'raises an error' do
+ expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user)
+
+ expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}'
+ end
+
+ it "doesn't raise an error if an admin user is impersonating a blocked user (via sudo)" do
+ admin_user = build(:user, :admin)
+
+ expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(admin_user)
+
+ expect { authenticate! }.not_to raise_error
+ end
+ end
end
end
diff --git a/spec/requests/api/v3/deployments_spec.rb b/spec/requests/api/v3/deployments_spec.rb
index 694786c3046..0389a264781 100644
--- a/spec/requests/api/v3/deployments_spec.rb
+++ b/spec/requests/api/v3/deployments_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::Deployments do
+describe API::V3::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:project) { deployment.environment.project }
@@ -24,11 +24,11 @@ describe API::Deployments do
describe 'GET /projects/:id/deployments' do
context 'as member of the project' do
it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/deployments", user) }
+ let(:request) { get v3_api("/projects/#{project.id}/deployments", user) }
end
it 'returns projects deployments' do
- get api("/projects/#{project.id}/deployments", user)
+ get v3_api("/projects/#{project.id}/deployments", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -40,7 +40,7 @@ describe API::Deployments do
context 'as non member' do
it 'returns a 404 status code' do
- get api("/projects/#{project.id}/deployments", non_member)
+ get v3_api("/projects/#{project.id}/deployments", non_member)
expect(response).to have_http_status(404)
end
@@ -50,7 +50,7 @@ describe API::Deployments do
describe 'GET /projects/:id/deployments/:deployment_id' do
context 'as a member of the project' do
it 'returns the projects deployment' do
- get api("/projects/#{project.id}/deployments/#{deployment.id}", user)
+ get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", user)
expect(response).to have_http_status(200)
expect(json_response['sha']).to match /\A\h{40}\z/
@@ -60,7 +60,7 @@ describe API::Deployments do
context 'as non member' do
it 'returns a 404 status code' do
- get api("/projects/#{project.id}/deployments/#{deployment.id}", non_member)
+ get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", non_member)
expect(response).to have_http_status(404)
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 7a07ea618c0..033e6ecd18c 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -27,6 +27,22 @@ describe Projects::CreateService, '#execute', services: true do
end
end
+ context "admin creates project with other user's namespace_id" do
+ it 'sets the correct permissions' do
+ admin = create(:admin)
+ opts = {
+ name: 'GitLab',
+ namespace_id: user.namespace.id
+ }
+ project = create_project(admin, opts)
+
+ expect(project).to be_persisted
+ expect(project.owner).to eq(user)
+ expect(project.team.masters).to include(user, admin)
+ expect(project.namespace).to eq(user.namespace)
+ end
+ end
+
context 'group namespace' do
let(:group) do
create(:group).tap do |group|
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 42d63a9f9ba..75d7caf2508 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -595,7 +595,7 @@ describe SystemNoteService, services: true do
end
shared_examples 'cross project mentionable' do
- include GitlabMarkdownHelper
+ include MarkupHelper
it 'contains cross reference to new noteable' do
expect(subject.note).to include cross_project_reference(new_project, new_noteable)
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index daea0c6bb37..0a4a6ed8145 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -350,7 +350,7 @@ describe 'gitlab:app namespace rake task' do
end
it 'name has human readable time' do
- expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_gitlab_backup.tar$/)
+ expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_\d+\.\d+\.\d+(-pre)?_gitlab_backup.tar$/)
end
end
end # gitlab:app namespace
diff --git a/vendor/Dockerfile/CONTRIBUTING.md b/vendor/Dockerfile/CONTRIBUTING.md
new file mode 100644
index 00000000000..91b92eafa1b
--- /dev/null
+++ b/vendor/Dockerfile/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+The canonical repository for `Dockerfile` templates is
+https://gitlab.com/gitlab-org/Dockerfile.
+
+GitLab only mirrors the templates. Please submit your merge requests to
+https://gitlab.com/gitlab-org/Dockerfile.
diff --git a/vendor/dockerfile/HTTPdDockerfile b/vendor/Dockerfile/HTTPd.Dockerfile
index 2f05427323c..2f05427323c 100644
--- a/vendor/dockerfile/HTTPdDockerfile
+++ b/vendor/Dockerfile/HTTPd.Dockerfile
diff --git a/vendor/Dockerfile/LICENSE b/vendor/Dockerfile/LICENSE
new file mode 100644
index 00000000000..d6c93c6fcf7
--- /dev/null
+++ b/vendor/Dockerfile/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2017 GitLab.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/Dockerfile/PHP.Dockerfile b/vendor/Dockerfile/PHP.Dockerfile
new file mode 100644
index 00000000000..6b098efcd85
--- /dev/null
+++ b/vendor/Dockerfile/PHP.Dockerfile
@@ -0,0 +1,14 @@
+FROM php:7.0-apache
+
+# Customize any core extensions here
+#RUN apt-get update && apt-get install -y \
+# libfreetype6-dev \
+# libjpeg62-turbo-dev \
+# libmcrypt-dev \
+# libpng12-dev \
+# && docker-php-ext-install -j$(nproc) iconv mcrypt \
+# && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+# && docker-php-ext-install -j$(nproc) gd
+
+COPY config/php.ini /usr/local/etc/php/
+COPY src/ /var/www/html/
diff --git a/vendor/Dockerfile/Python2.Dockerfile b/vendor/Dockerfile/Python2.Dockerfile
new file mode 100644
index 00000000000..c9a03584d40
--- /dev/null
+++ b/vendor/Dockerfile/Python2.Dockerfile
@@ -0,0 +1,11 @@
+FROM python:2.7
+
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+COPY requirements.txt /usr/src/app/
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . /usr/src/app
+
+CMD ["python", "app.py"]