summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml4
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--app/assets/javascripts/blob/file_template_mediator.js241
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js60
-rw-r--r--app/assets/javascripts/blob/template_selector.js (renamed from app/assets/javascripts/blob/template_selectors/template_selector.js)0
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js9
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js23
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_license_selector.js13
-rw-r--r--app/assets/javascripts/blob/template_selectors/blob_license_selectors.js24
-rw-r--r--app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js32
-rw-r--r--app/assets/javascripts/blob/template_selectors/dockerfile_selector.js32
-rw-r--r--app/assets/javascripts/blob/template_selectors/gitignore_selector.js31
-rw-r--r--app/assets/javascripts/blob/template_selectors/license_selector.js38
-rw-r--r--app/assets/javascripts/blob/template_selectors/type_selector.js25
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js3
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js38
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js2
-rw-r--r--app/assets/stylesheets/pages/editor.scss142
-rw-r--r--app/controllers/concerns/issuable_collections.rb3
-rw-r--r--app/controllers/projects/git_http_controller.rb2
-rw-r--r--app/mailers/base_mailer.rb4
-rw-r--r--app/models/ci/pipeline.rb5
-rw-r--r--app/models/concerns/importable.rb3
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/repository_mirroring.rb17
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/notification_setting.rb17
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_services/kubernetes_service.rb43
-rw-r--r--app/models/repository.rb28
-rw-r--r--app/models/service.rb2
-rw-r--r--app/models/system_note_metadata.rb4
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/ci/process_pipeline_service.rb15
-rw-r--r--app/services/notification_recipient_service.rb42
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/projects/import_service.rb31
-rw-r--r--app/services/projects/update_pages_service.rb1
-rw-r--r--app/services/search/global_service.rb6
-rw-r--r--app/services/system_note_service.rb14
-rw-r--r--app/views/projects/blob/_editor.html.haml20
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml17
-rw-r--r--app/views/projects/blob/edit.html.haml7
-rw-r--r--app/views/projects/blob/new.html.haml8
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/shared/icons/_activity.svg16
-rw-r--r--app/views/shared/icons/_commits.svg10
-rw-r--r--app/views/shared/icons/_contributionanalytics.svg17
-rw-r--r--app/views/shared/icons/_delta.svg3
-rw-r--r--app/views/shared/icons/_files.svg17
-rw-r--r--app/views/shared/icons/_icon_close.svg2
-rw-r--r--app/views/shared/icons/_icon_empty_groups.svg2
-rw-r--r--app/views/shared/icons/_icon_mr_issue.svg2
-rw-r--r--app/views/shared/icons/_icon_play.svg4
-rw-r--r--app/views/shared/icons/_icon_stopwatch.svg2
-rw-r--r--app/views/shared/icons/_icon_timer.svg2
-rw-r--r--app/views/shared/icons/_illustration_no_commits.svg2
-rw-r--r--app/views/shared/icons/_members.svg13
-rw-r--r--app/views/shared/icons/_milestones.svg15
-rw-r--r--app/views/shared/icons/_mr.svg13
-rw-r--r--app/views/shared/icons/_pipelines.svg10
-rw-r--r--app/views/shared/icons/_wiki.svg10
-rw-r--r--app/views/shared/notifications/_custom_notifications.html.haml2
-rw-r--r--app/workers/process_commit_worker.rb2
-rw-r--r--app/workers/repository_import_worker.rb1
-rw-r--r--changelogs/unreleased/22303-symbolic-in-tree.yml4
-rw-r--r--changelogs/unreleased/24187-set-git-terminal-prompt-env-var-in-initializer.yml4
-rw-r--r--changelogs/unreleased/25332-make-file-templates-easy-to-use-and-discover.yml4
-rw-r--r--changelogs/unreleased/29492-useless-queries.yml4
-rw-r--r--changelogs/unreleased/29670-jira-integration-documentation-improvment.yml4
-rw-r--r--changelogs/unreleased/backport-sticking-api-helper-changes.yml4
-rw-r--r--changelogs/unreleased/fix-gb-remove-deprecated-pipeline-processing-code.yml4
-rw-r--r--changelogs/unreleased/fix-github-importer-slowness.yml4
-rw-r--r--changelogs/unreleased/namespace-race-condition.yml4
-rw-r--r--changelogs/unreleased/pages-debug-log.yml4
-rw-r--r--changelogs/unreleased/quiet-pipelines.yml5
-rw-r--r--changelogs/unreleased/remove_index_for_users-current_sign_in_at.yml4
-rw-r--r--changelogs/unreleased/style-proc-cop.yml4
-rw-r--r--changelogs/unreleased/zj-kube-service-auto-fill.yml4
-rw-r--r--config/application.rb1
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/8_gitaly.rb2
-rw-r--r--config/initializers/bullet.rb13
-rw-r--r--db/fixtures/development/18_abuse_reports.rb28
-rw-r--r--db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb25
-rw-r--r--db/schema.rb3
-rw-r--r--doc/development/img/cache-hit.svg21
-rw-r--r--doc/development/img/cache-miss.svg24
-rw-r--r--doc/development/polling.md3
-rw-r--r--doc/install/requirements.md3
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/user/project/integrations/img/jira_project_settings.pngbin0 -> 32791 bytes
-rw-r--r--doc/user/project/integrations/jira.md5
-rw-r--r--doc/workflow/notifications.md7
-rw-r--r--features/steps/project/hooks.rb4
-rw-r--r--features/support/capybara.rb3
-rw-r--r--features/support/env.rb4
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/helpers/runner.rb6
-rw-r--r--lib/api/runner.rb15
-rw-r--r--lib/ci/api/builds.rb15
-rw-r--r--lib/ci/api/helpers.rb6
-rw-r--r--lib/gitlab/git/tree.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb4
-rw-r--r--lib/gitlab/github_import/importer.rb2
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb3
-rw-r--r--lib/gitlab/ldap/config.rb4
-rw-r--r--lib/gitlab/shell.rb25
-rw-r--r--lib/gitlab/workhorse.rb17
-rw-r--r--spec/controllers/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/factories/chat_names.rb8
-rw-r--r--spec/factories/chat_teams.rb5
-rw-r--r--spec/factories/ci/pipelines.rb4
-rw-r--r--spec/factories/ci/runners.rb4
-rw-r--r--spec/factories/emails.rb2
-rw-r--r--spec/factories/issues.rb6
-rw-r--r--spec/factories/labels.rb11
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/oauth_applications.rb4
-rw-r--r--spec/factories/personal_access_tokens.rb2
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/factories/sequences.rb12
-rw-r--r--spec/factories/service_hooks.rb2
-rw-r--r--spec/factories/snippets.rb14
-rw-r--r--spec/factories/spam_logs.rb6
-rw-r--r--spec/factories/system_hooks.rb2
-rw-r--r--spec/factories/users.rb8
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb2
-rw-r--r--spec/features/auto_deploy_spec.rb4
-rw-r--r--spec/features/issuables/issuable_list_spec.rb9
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb22
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb4
-rw-r--r--spec/features/projects/blobs/user_create_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb4
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb135
-rw-r--r--spec/features/projects/files/undo_template_spec.rb67
-rw-r--r--spec/features/u2f_spec.rb17
-rw-r--r--spec/initializers/trusted_proxies_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb1
-rw-r--r--spec/lib/gitlab/git_access_spec.rb8
-rw-r--r--spec/lib/gitlab/git_spec.rb17
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb26
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb9
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb60
-rw-r--r--spec/mailers/notify_spec.rb4
-rw-r--r--spec/models/ci/pipeline_spec.rb7
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb4
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb89
-rw-r--r--spec/models/repository_spec.rb25
-rw-r--r--spec/requests/api/files_spec.rb17
-rw-r--r--spec/requests/api/issues_spec.rb6
-rw-r--r--spec/requests/api/members_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/v3/files_spec.rb4
-rw-r--r--spec/requests/api/v3/issues_spec.rb6
-rw-r--r--spec/requests/api/v3/members_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb2
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb59
-rw-r--r--spec/services/notification_service_spec.rb190
-rw-r--r--spec/services/projects/import_service_spec.rb83
-rw-r--r--spec/services/system_note_service_spec.rb35
-rw-r--r--spec/support/cycle_analytics_helpers.rb10
-rw-r--r--spec/support/filter_spec_helper.rb4
-rw-r--r--spec/support/git_helpers.rb9
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb19
-rw-r--r--spec/support/matchers/query_matcher.rb33
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb2
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb126
-rw-r--r--spec/workers/process_commit_worker_spec.rb7
182 files changed, 1836 insertions, 962 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index fa1370ea1f3..ac6b141cea3 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -533,6 +533,10 @@ Style/WhileUntilModifier:
Style/WordArray:
Enabled: true
+# Use `proc` instead of `Proc.new`.
+Style/Proc:
+ Enabled: true
+
# Metrics #####################################################################
# A calculated magnitude based on number of assignments,
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index c24142c0a11..8588988dc87 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -226,11 +226,6 @@ Style/PredicateName:
Style/PreferredHashMethods:
Enabled: false
-# Offense count: 8
-# Cop supports --auto-correct.
-Style/Proc:
- Enabled: false
-
# Offense count: 62
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 1d0ba9ea182..8f0916f768f 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.4.0
+0.5.0
diff --git a/Gemfile b/Gemfile
index 0c6bb805b98..910ab318227 100644
--- a/Gemfile
+++ b/Gemfile
@@ -260,7 +260,6 @@ group :development do
gem 'brakeman', '~> 3.6.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
- gem 'bullet', '~> 5.5.0', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler
@@ -272,6 +271,7 @@ group :development do
end
group :development, :test do
+ gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET']
gem 'pry-byebug', '~> 3.4.1', platform: :mri
gem 'pry-rails', '~> 0.3.4'
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
new file mode 100644
index 00000000000..3062cd51ee3
--- /dev/null
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -0,0 +1,241 @@
+/* eslint-disable class-methods-use-this */
+/* global Flash */
+
+import FileTemplateTypeSelector from './template_selectors/type_selector';
+import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
+import DockerfileSelector from './template_selectors/dockerfile_selector';
+import GitignoreSelector from './template_selectors/gitignore_selector';
+import LicenseSelector from './template_selectors/license_selector';
+
+export default class FileTemplateMediator {
+ constructor({ editor, currentAction }) {
+ this.editor = editor;
+ this.currentAction = currentAction;
+
+ this.initTemplateSelectors();
+ this.initTemplateTypeSelector();
+ this.initDomElements();
+ this.initDropdowns();
+ this.initPageEvents();
+ }
+
+ initTemplateSelectors() {
+ // Order dictates template type dropdown item order
+ this.templateSelectors = [
+ GitignoreSelector,
+ BlobCiYamlSelector,
+ DockerfileSelector,
+ LicenseSelector,
+ ].map(TemplateSelectorClass => new TemplateSelectorClass({ mediator: this }));
+ }
+
+ initTemplateTypeSelector() {
+ this.typeSelector = new FileTemplateTypeSelector({
+ mediator: this,
+ dropdownData: this.templateSelectors
+ .map((templateSelector) => {
+ const cfg = templateSelector.config;
+
+ return {
+ name: cfg.name,
+ key: cfg.key,
+ };
+ }),
+ });
+ }
+
+ initDomElements() {
+ const $templatesMenu = $('.template-selectors-menu');
+ const $undoMenu = $templatesMenu.find('.template-selectors-undo-menu');
+ const $fileEditor = $('.file-editor');
+
+ this.$templatesMenu = $templatesMenu;
+ this.$undoMenu = $undoMenu;
+ this.$undoBtn = $undoMenu.find('button');
+ this.$templateSelectors = $templatesMenu.find('.template-selector-dropdowns-wrap');
+ this.$filenameInput = $fileEditor.find('.js-file-path-name-input');
+ this.$fileContent = $fileEditor.find('#file-content');
+ this.$commitForm = $fileEditor.find('form');
+ this.$navLinks = $fileEditor.find('.nav-links');
+ }
+
+ initDropdowns() {
+ if (this.currentAction === 'create') {
+ this.typeSelector.show();
+ } else {
+ this.hideTemplateSelectorMenu();
+ }
+
+ this.displayMatchedTemplateSelector();
+ }
+
+ initPageEvents() {
+ this.listenForFilenameInput();
+ this.prepFileContentForSubmit();
+ this.listenForPreviewMode();
+ }
+
+ listenForFilenameInput() {
+ this.$filenameInput.on('keyup blur', () => {
+ this.displayMatchedTemplateSelector();
+ });
+ }
+
+ prepFileContentForSubmit() {
+ this.$commitForm.submit(() => {
+ this.$fileContent.val(this.editor.getValue());
+ });
+ }
+
+ listenForPreviewMode() {
+ this.$navLinks.on('click', 'a', (e) => {
+ const urlPieces = e.target.href.split('#');
+ const hash = urlPieces[1];
+ if (hash === 'preview') {
+ this.hideTemplateSelectorMenu();
+ } else if (hash === 'editor') {
+ this.showTemplateSelectorMenu();
+ }
+ });
+ }
+
+ selectTemplateType(item, el, e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.templateSelectors.forEach((selector) => {
+ if (selector.config.key === item.key) {
+ selector.show();
+ } else {
+ selector.hide();
+ }
+ });
+
+ this.typeSelector.setToggleText(item.name);
+
+ this.cacheToggleText();
+ }
+
+ selectTemplateFile(selector, query, data) {
+ selector.renderLoading();
+ // in case undo menu is already already there
+ this.destroyUndoMenu();
+ this.fetchFileTemplate(selector.config.endpoint, query, data)
+ .then((file) => {
+ this.showUndoMenu();
+ this.setEditorContent(file);
+ this.setFilename(selector.config.name);
+ selector.renderLoaded();
+ })
+ .catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
+ }
+
+ displayMatchedTemplateSelector() {
+ const currentInput = this.getFilename();
+ this.templateSelectors.forEach((selector) => {
+ const match = selector.config.pattern.test(currentInput);
+
+ if (match) {
+ this.typeSelector.show();
+ this.selectTemplateType(selector.config);
+ this.showTemplateSelectorMenu();
+ }
+ });
+ }
+
+ fetchFileTemplate(apiCall, query, data) {
+ return new Promise((resolve) => {
+ const resolveFile = file => resolve(file);
+
+ if (!data) {
+ apiCall(query, resolveFile);
+ } else {
+ apiCall(query, data, resolveFile);
+ }
+ });
+ }
+
+ setEditorContent(file) {
+ if (!file && file !== '') return;
+
+ const newValue = file.content || file;
+
+ this.editor.setValue(newValue, 1);
+
+ this.editor.focus();
+
+ this.editor.navigateFileStart();
+ }
+
+ findTemplateSelectorByKey(key) {
+ return this.templateSelectors.find(selector => selector.config.key === key);
+ }
+
+ showUndoMenu() {
+ this.$undoMenu.removeClass('hidden');
+
+ this.$undoBtn.on('click', () => {
+ this.restoreFromCache();
+ this.destroyUndoMenu();
+ });
+ }
+
+ destroyUndoMenu() {
+ this.cacheFileContents();
+ this.cacheToggleText();
+ this.$undoMenu.addClass('hidden');
+ this.$undoBtn.off('click');
+ }
+
+ hideTemplateSelectorMenu() {
+ this.$templatesMenu.hide();
+ }
+
+ showTemplateSelectorMenu() {
+ this.$templatesMenu.show();
+ }
+
+ cacheToggleText() {
+ this.cachedToggleText = this.getTemplateSelectorToggleText();
+ }
+
+ cacheFileContents() {
+ this.cachedContent = this.editor.getValue();
+ this.cachedFilename = this.getFilename();
+ }
+
+ restoreFromCache() {
+ this.setEditorContent(this.cachedContent);
+ this.setFilename(this.cachedFilename);
+ this.setTemplateSelectorToggleText();
+ }
+
+ getTemplateSelectorToggleText() {
+ return this.$templateSelectors
+ .find('.js-template-selector-wrap:visible .dropdown-toggle-text')
+ .text();
+ }
+
+ setTemplateSelectorToggleText() {
+ return this.$templateSelectors
+ .find('.js-template-selector-wrap:visible .dropdown-toggle-text')
+ .text(this.cachedToggleText);
+ }
+
+ getTypeSelectorToggleText() {
+ return this.typeSelector.getToggleText();
+ }
+
+ getFilename() {
+ return this.$filenameInput.val();
+ }
+
+ setFilename(name) {
+ this.$filenameInput.val(name);
+ }
+
+ getSelected() {
+ return this.templateSelectors.find(selector => selector.selected);
+ }
+}
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
new file mode 100644
index 00000000000..31dd45fac89
--- /dev/null
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -0,0 +1,60 @@
+/* global Api */
+
+export default class FileTemplateSelector {
+ constructor(mediator) {
+ this.mediator = mediator;
+ this.$dropdown = null;
+ this.$wrapper = null;
+ }
+
+ init() {
+ const cfg = this.config;
+
+ this.$dropdown = $(cfg.dropdown);
+ this.$wrapper = $(cfg.wrapper);
+ this.$loadingIcon = this.$wrapper.find('.fa-chevron-down');
+ this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text');
+
+ this.initDropdown();
+ }
+
+ show() {
+ if (this.$dropdown === null) {
+ this.init();
+ }
+
+ this.$wrapper.removeClass('hidden');
+ }
+
+ hide() {
+ if (this.$dropdown !== null) {
+ this.$wrapper.addClass('hidden');
+ }
+ }
+
+ getToggleText() {
+ return this.$dropdownToggleText.text();
+ }
+
+ setToggleText(text) {
+ this.$dropdownToggleText.text(text);
+ }
+
+ renderLoading() {
+ this.$loadingIcon
+ .addClass('fa-spinner fa-spin')
+ .removeClass('fa-chevron-down');
+ }
+
+ renderLoaded() {
+ this.$loadingIcon
+ .addClass('fa-chevron-down')
+ .removeClass('fa-spinner fa-spin');
+ }
+
+ reportSelection(query, el, e, data) {
+ e.preventDefault();
+ return this.mediator.selectTemplateFile(this, query, data);
+ }
+}
+
diff --git a/app/assets/javascripts/blob/template_selectors/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index d7c1c32efbd..d7c1c32efbd 100644
--- a/app/assets/javascripts/blob/template_selectors/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
deleted file mode 100644
index 5a5954e7751..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobCiYamlSelector extends TemplateSelector {
- requestFile(query) {
- return Api.gitlabCiYml(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
deleted file mode 100644
index 7a4d6a42a03..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* global Api */
-
-import BlobCiYamlSelector from './blob_ci_yaml_selector';
-
-export default class BlobCiYamlSelectors {
- constructor({ editor, $dropdowns }) {
- this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
- this.initSelectors(editor);
- }
-
- initSelectors(editor) {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
- return new BlobCiYamlSelector({
- editor,
- pattern: /(.gitlab-ci.yml)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
deleted file mode 100644
index 19f8820a0cb..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobDockerfileSelector extends TemplateSelector {
- requestFile(query) {
- return Api.dockerfileYml(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
deleted file mode 100644
index da067035b43..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import BlobDockerfileSelector from './blob_dockerfile_selector';
-
-export default class BlobDockerfileSelectors {
- constructor({ editor, $dropdowns }) {
- this.editor = editor;
- this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
- this.initSelectors();
- }
-
- initSelectors() {
- const editor = this.editor;
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
- return new BlobDockerfileSelector({
- editor,
- pattern: /(Dockerfile)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
deleted file mode 100644
index 0b6b02fc2b3..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobGitignoreSelector extends TemplateSelector {
- requestFile(query) {
- return Api.gitignoreText(query.name, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
deleted file mode 100644
index dc485d97677..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import BlobGitignoreSelector from './blob_gitignore_selector';
-
-export default class BlobGitignoreSelectors {
- constructor({ editor, $dropdowns }) {
- this.$dropdowns = $dropdowns || $('.js-gitignore-selector');
- this.editor = editor;
- this.initSelectors();
- }
-
- initSelectors() {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
-
- return new BlobGitignoreSelector({
- pattern: /(.gitignore)/,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
- dropdown: $dropdown,
- editor: this.editor,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
deleted file mode 100644
index e9cb31cc2dc..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* global Api */
-
-import TemplateSelector from './template_selector';
-
-export default class BlobLicenseSelector extends TemplateSelector {
- requestFile(query) {
- const data = {
- project: this.dropdown.data('project'),
- fullname: this.dropdown.data('fullname'),
- };
- return Api.licenseText(query.id, data, (file, config) => this.setEditorContent(file, config));
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
deleted file mode 100644
index a44f4f78b2d..00000000000
--- a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable no-unused-vars, no-param-reassign */
-
-import BlobLicenseSelector from './blob_license_selector';
-
-export default class BlobLicenseSelectors {
- constructor({ $dropdowns, editor }) {
- this.$dropdowns = $dropdowns || $('.js-license-selector');
- this.initSelectors(editor);
- }
-
- initSelectors(editor) {
- this.$dropdowns.each((i, dropdown) => {
- const $dropdown = $(dropdown);
-
- return new BlobLicenseSelector({
- editor,
- pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
- data: $dropdown.data('data'),
- wrapper: $dropdown.closest('.js-license-selector-wrap'),
- dropdown: $dropdown,
- });
- });
- }
-}
diff --git a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js
new file mode 100644
index 00000000000..935df07677c
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js
@@ -0,0 +1,32 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobCiYamlSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'gitlab-ci-yaml',
+ name: '.gitlab-ci.yml',
+ pattern: /(.gitlab-ci.yml)/,
+ endpoint: Api.gitlabCiYml,
+ dropdown: '.js-gitlab-ci-yml-selector',
+ wrapper: '.js-gitlab-ci-yml-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ // maybe move to super class as well
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
new file mode 100644
index 00000000000..b4b4d09c315
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js
@@ -0,0 +1,32 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class DockerfileSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'dockerfile',
+ name: 'Dockerfile',
+ pattern: /(Dockerfile)/,
+ endpoint: Api.dockerfileYml,
+ dropdown: '.js-dockerfile-selector',
+ wrapper: '.js-dockerfile-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ // maybe move to super class as well
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js
new file mode 100644
index 00000000000..aefae54ae71
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js
@@ -0,0 +1,31 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobGitignoreSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'gitignore',
+ name: '.gitignore',
+ pattern: /(.gitignore)/,
+ endpoint: Api.gitignoreText,
+ dropdown: '.js-gitignore-selector',
+ wrapper: '.js-gitignore-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => this.reportSelection(query.name, el, e),
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js
new file mode 100644
index 00000000000..c8abd689ab4
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/license_selector.js
@@ -0,0 +1,38 @@
+/* global Api */
+
+import FileTemplateSelector from '../file_template_selector';
+
+export default class BlobLicenseSelector extends FileTemplateSelector {
+ constructor({ mediator }) {
+ super(mediator);
+ this.config = {
+ key: 'license',
+ name: 'LICENSE',
+ pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
+ endpoint: Api.licenseText,
+ dropdown: '.js-license-selector',
+ wrapper: '.js-license-selector-wrap',
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.$dropdown.data('data'),
+ filterable: true,
+ selectable: true,
+ toggleLabel: item => item.name,
+ search: {
+ fields: ['name'],
+ },
+ clicked: (query, el, e) => {
+ const data = {
+ project: this.$dropdown.data('project'),
+ fullname: this.$dropdown.data('fullname'),
+ };
+
+ this.reportSelection(query.id, el, e, data);
+ },
+ text: item => item.name,
+ });
+ }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/type_selector.js b/app/assets/javascripts/blob/template_selectors/type_selector.js
new file mode 100644
index 00000000000..56f23ef0568
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/type_selector.js
@@ -0,0 +1,25 @@
+import FileTemplateSelector from '../file_template_selector';
+
+export default class FileTemplateTypeSelector extends FileTemplateSelector {
+ constructor({ mediator, dropdownData }) {
+ super(mediator);
+ this.mediator = mediator;
+ this.config = {
+ dropdown: '.js-template-type-selector',
+ wrapper: '.js-template-type-selector-wrap',
+ dropdownData,
+ };
+ }
+
+ initDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.config.dropdownData,
+ filterable: false,
+ selectable: true,
+ toggleLabel: item => item.name,
+ clicked: (item, el, e) => this.mediator.selectTemplateType(item, el, e),
+ text: item => item.name,
+ });
+ }
+
+}
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index c5deccf631e..1c64ccf536f 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -13,8 +13,9 @@ $(() => {
const urlRoot = editBlobForm.data('relative-url-root');
const assetsPath = editBlobForm.data('assets-prefix');
const blobLanguage = editBlobForm.data('blob-language');
+ const currentAction = $('.js-file-title').data('current-action');
- new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage);
+ new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction);
new NewCommitForm(editBlobForm);
}
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index d3560d5df3b..b37988a674d 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,17 +1,13 @@
/* global ace */
-import BlobLicenseSelectors from '../blob/template_selectors/blob_license_selectors';
-import BlobGitignoreSelectors from '../blob/template_selectors/blob_gitignore_selectors';
-import BlobCiYamlSelectors from '../blob/template_selectors/blob_ci_yaml_selectors';
-import BlobDockerfileSelectors from '../blob/template_selectors/blob_dockerfile_selectors';
+import TemplateSelectorMediator from '../blob/file_template_mediator';
export default class EditBlob {
- constructor(assetsPath, aceMode) {
+ constructor(assetsPath, aceMode, currentAction) {
this.configureAceEditor(aceMode, assetsPath);
- this.prepFileContentForSubmit();
this.initModePanesAndLinks();
this.initSoftWrap();
- this.initFileSelectors();
+ this.initFileSelectors(currentAction);
}
configureAceEditor(aceMode, assetsPath) {
@@ -19,6 +15,10 @@ export default class EditBlob {
ace.config.loadModule('ace/ext/searchbox');
this.editor = ace.edit('editor');
+
+ // This prevents warnings re: automatic scrolling being logged
+ this.editor.$blockScrolling = Infinity;
+
this.editor.focus();
if (aceMode) {
@@ -26,29 +26,13 @@ export default class EditBlob {
}
}
- prepFileContentForSubmit() {
- $('form').submit(() => {
- $('#file-content').val(this.editor.getValue());
+ initFileSelectors(currentAction) {
+ this.fileTemplateMediator = new TemplateSelectorMediator({
+ currentAction,
+ editor: this.editor,
});
}
- initFileSelectors() {
- this.blobTemplateSelectors = [
- new BlobLicenseSelectors({
- editor: this.editor,
- }),
- new BlobGitignoreSelectors({
- editor: this.editor,
- }),
- new BlobCiYamlSelectors({
- editor: this.editor,
- }),
- new BlobDockerfileSelectors({
- editor: this.editor,
- }),
- ];
- }
-
initModePanesAndLinks() {
this.$editModePanes = $('.js-edit-mode-pane');
this.$editModeLinks = $('.js-edit-mode a');
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 32067ed1fee..e62f429f1ae 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -1,7 +1,7 @@
/* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */
/* global Api */
-import TemplateSelector from '../blob/template_selectors/template_selector';
+import TemplateSelector from '../blob/template_selector';
((global) => {
class IssuableTemplateSelector extends TemplateSelector {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 4af267403d8..f6b8c8ee2bc 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -1,4 +1,13 @@
.file-editor {
+ .nav-links {
+ border-top: 1px solid $border-color;
+ border-right: 1px solid $border-color;
+ border-left: 1px solid $border-color;
+ border-bottom: none;
+ border-radius: 2px;
+ background: $gray-normal;
+ }
+
#editor {
border: none;
border-radius: 0;
@@ -72,11 +81,7 @@
}
.encoding-selector,
- .soft-wrap-toggle,
- .license-selector,
- .gitignore-selector,
- .gitlab-ci-yml-selector,
- .dockerfile-selector {
+ .soft-wrap-toggle {
display: inline-block;
vertical-align: top;
font-family: $regular_font;
@@ -103,28 +108,9 @@
}
}
}
-
- .gitignore-selector,
- .license-selector,
- .gitlab-ci-yml-selector,
- .dockerfile-selector {
- .dropdown {
- line-height: 21px;
- }
-
- .dropdown-menu-toggle {
- vertical-align: top;
- width: 220px;
- }
- }
-
- .gitlab-ci-yml-selector {
- .dropdown-menu-toggle {
- width: 250px;
- }
- }
}
+
@media(max-width: $screen-xs-max){
.file-editor {
.file-title {
@@ -149,10 +135,7 @@
margin: 3px 0;
}
- .encoding-selector,
- .license-selector,
- .gitignore-selector,
- .gitlab-ci-yml-selector {
+ .encoding-selector {
display: block;
margin: 3px 0;
@@ -163,3 +146,104 @@
}
}
}
+
+.blob-new-page-title,
+.blob-edit-page-title {
+ margin: 19px 0 21px;
+ vertical-align: top;
+ display: inline-block;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ margin: 19px 0 12px;
+ }
+}
+
+.template-selectors-menu {
+ display: inline-block;
+ vertical-align: top;
+ margin: 14px 0 0 16px;
+ padding: 0 0 0 14px;
+ border-left: 1px solid $border-color;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ width: 100%;
+ margin: 5px 0;
+ padding: 0;
+ border-left: none;
+ }
+}
+
+.templates-selectors-label {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 6px;
+ line-height: 21px;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ margin: 5px 0;
+ }
+}
+
+.template-selector-dropdowns-wrap {
+ display: inline-block;
+ margin-left: 8px;
+ vertical-align: top;
+ margin: 5px 0 0 8px;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ width: 100%;
+ margin: 0 0 16px;
+ }
+
+ .license-selector,
+ .gitignore-selector,
+ .gitlab-ci-yml-selector,
+ .dockerfile-selector,
+ .template-type-selector {
+ display: inline-block;
+ vertical-align: top;
+ font-family: $regular_font;
+ margin-top: -5px;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ width: 100%;
+ margin: 5px 0;
+ }
+
+ .dropdown {
+ line-height: 21px;
+ }
+
+ .dropdown-menu-toggle {
+ width: 250px;
+ vertical-align: top;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ width: 100%;
+ margin: 5px 0;
+ }
+ }
+
+ }
+}
+
+.template-selectors-undo-menu {
+ display: inline-block;
+ margin: 7px 0 0 10px;
+
+ @media(max-width: $screen-sm-max) {
+ display: block;
+ width: 100%;
+ margin: 20px 0;
+ }
+
+ button {
+ margin: -4px 0 0 15px;
+ }
+}
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 85ae4985e58..c8a501d7319 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -15,6 +15,9 @@ module IssuableCollections
# a new order into the collection.
# We cannot use reorder to not mess up the paginated collection.
issuable_ids = issuable_collection.map(&:id)
+
+ return {} if issuable_ids.empty?
+
issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type)
issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type)
issuable_merge_requests_count =
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 278098fcc58..37f6f637ff0 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -57,7 +57,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def render_ok
set_workhorse_internal_api_content_type
- render json: Gitlab::Workhorse.git_http_ok(repository, user)
+ render json: Gitlab::Workhorse.git_http_ok(repository, user, action_name)
end
def render_http_not_allowed
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 79c3c2e62c5..a9b6b33eb5c 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -5,8 +5,8 @@ class BaseMailer < ActionMailer::Base
attr_accessor :current_user
helper_method :current_user, :can?
- default from: Proc.new { default_sender_address.format }
- default reply_to: Proc.new { default_reply_to_address.format }
+ default from: proc { default_sender_address.format }
+ default reply_to: proc { default_reply_to_address.format }
def can?
Ability.allowed?(current_user, action, subject)
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index ad7e0b23ff4..49dec770096 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -164,11 +164,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
- # For now the only user who participates is the user who triggered
- def participants(_current_user = nil)
- Array(user)
- end
-
def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
diff --git a/app/models/concerns/importable.rb b/app/models/concerns/importable.rb
index 019ef755849..c9331eaf4cc 100644
--- a/app/models/concerns/importable.rb
+++ b/app/models/concerns/importable.rb
@@ -3,4 +3,7 @@ module Importable
attr_accessor :importing
alias_method :importing?, :importing
+
+ attr_accessor :imported
+ alias_method :imported?, :imported
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 4d54426b79e..b4dded7e27e 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -14,6 +14,7 @@ module Issuable
include Awardable
include Taskable
include TimeTrackable
+ include Importable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
@@ -99,7 +100,7 @@ module Issuable
acts_as_paranoid
after_save :update_assignee_cache_counts, if: :assignee_id_changed?
- after_save :record_metrics
+ after_save :record_metrics, unless: :imported?
def update_assignee_cache_counts
# make sure we flush the cache for both the old *and* new assignees(if they exist)
diff --git a/app/models/concerns/repository_mirroring.rb b/app/models/concerns/repository_mirroring.rb
new file mode 100644
index 00000000000..fed336c29d6
--- /dev/null
+++ b/app/models/concerns/repository_mirroring.rb
@@ -0,0 +1,17 @@
+module RepositoryMirroring
+ def set_remote_as_mirror(name)
+ config = raw_repository.rugged.config
+
+ # This is used to define repository as equivalent as "git clone --mirror"
+ config["remote.#{name}.fetch"] = 'refs/*:refs/*'
+ config["remote.#{name}.mirror"] = true
+ config["remote.#{name}.prune"] = true
+ end
+
+ def fetch_mirror(remote, url)
+ add_remote(remote, url)
+ set_remote_as_mirror(remote)
+ fetch_remote(remote, forced: true)
+ remove_remote(remote)
+ end
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5ff83944d8c..8d740adb771 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -3,7 +3,6 @@ class MergeRequest < ActiveRecord::Base
include Issuable
include Referable
include Sortable
- include Importable
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index e85d5709624..ac205b9b738 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -30,7 +30,7 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true, uniqueness: { scope: :project_id }
validates :project, presence: true
- validate :start_date_should_be_less_than_due_date, if: Proc.new { |m| m.start_date.present? && m.due_date.present? }
+ validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
strip_attributes :title
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 52577bd52ea..e4726e62e93 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -60,16 +60,25 @@ class NotificationSetting < ActiveRecord::Base
def set_events
return if custom?
- EMAIL_EVENTS.each do |event|
- events[event] = false
- end
+ self.events = {}
end
# Validates store accessors values as boolean
# It is a text field so it does not cast correct boolean values in JSON
def events_to_boolean
EMAIL_EVENTS.each do |event|
- events[event] = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(events[event])
+ bool = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(public_send(event))
+
+ events[event] = bool
end
end
+
+ # Allow people to receive failed pipeline notifications if they already have
+ # custom notifications enabled, as these are more like mentions than the other
+ # custom settings.
+ def failed_pipeline
+ bool = super
+
+ bool.nil? || bool
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index f1bba56d32c..83660d8c431 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -551,6 +551,10 @@ class Project < ActiveRecord::Base
import_type == 'gitea'
end
+ def github_import?
+ import_type == 'github'
+ end
+
def check_limit
unless creator.can_create_project? || namespace.kind == 'group'
projects_limit = creator.projects_limit
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 02fbd5497fa..9c56518c991 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -22,22 +22,21 @@ class KubernetesService < DeploymentService
with_options presence: true, if: :activated? do
validates :api_url, url: true
validates :token
-
- validates :namespace,
- format: {
- with: Gitlab::Regex.kubernetes_namespace_regex,
- message: Gitlab::Regex.kubernetes_namespace_regex_message,
- },
- length: 1..63
end
+ validates :namespace,
+ allow_blank: true,
+ length: 1..63,
+ if: :activated?,
+ format: {
+ with: Gitlab::Regex.kubernetes_namespace_regex,
+ message: Gitlab::Regex.kubernetes_namespace_regex_message
+ }
+
after_save :clear_reactive_cache!
def initialize_properties
- if properties.nil?
- self.properties = {}
- self.namespace = "#{project.path}-#{project.id}" if project.present?
- end
+ self.properties = {} if properties.nil?
end
def title
@@ -62,7 +61,7 @@ class KubernetesService < DeploymentService
{ type: 'text',
name: 'namespace',
title: 'Kubernetes namespace',
- placeholder: 'Kubernetes namespace' },
+ placeholder: namespace_placeholder },
{ type: 'text',
name: 'api_url',
title: 'API URL',
@@ -92,7 +91,7 @@ class KubernetesService < DeploymentService
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: namespace, public: true }
+ { key: 'KUBE_NAMESPACE', value: namespace_variable, public: true }
]
if ca_pem.present?
@@ -135,8 +134,26 @@ class KubernetesService < DeploymentService
{ pods: pods }
end
+ TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
+
private
+ def namespace_placeholder
+ default_namespace || TEMPLATE_PLACEHOLDER
+ end
+
+ def namespace_variable
+ if namespace.present?
+ namespace
+ else
+ default_namespace
+ end
+ end
+
+ def default_namespace
+ "#{project.path}-#{project.id}" if project.present?
+ end
+
def build_kubeclient!(api_path: 'api', api_version: 'v1')
raise "Incomplete settings" unless api_url && namespace && token
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 596650353fc..6b2383b73eb 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -2,6 +2,7 @@ require 'securerandom'
class Repository
include Gitlab::ShellAdapter
+ include RepositoryMirroring
attr_accessor :path_with_namespace, :project
@@ -64,7 +65,7 @@ class Repository
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.expand_path(
- File.join(@project.repository_storage_path, path_with_namespace + ".git")
+ File.join(repository_storage_path, path_with_namespace + ".git")
)
end
@@ -401,10 +402,6 @@ class Repository
expire_tags_cache
end
- def before_import
- expire_content_cache
- end
-
# Runs code after the HEAD of a repository is changed.
def after_change_head
expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
@@ -1033,6 +1030,23 @@ class Repository
rugged.references.delete(tmp_ref) if tmp_ref
end
+ def add_remote(name, url)
+ raw_repository.remote_add(name, url)
+ rescue Rugged::ConfigError
+ raw_repository.remote_update(name, url: url)
+ end
+
+ def remove_remote(name)
+ raw_repository.remote_delete(name)
+ true
+ rescue Rugged::ConfigError
+ false
+ end
+
+ def fetch_remote(remote, forced: false, no_tags: false)
+ gitlab_shell.fetch_remote(repository_storage_path, path_with_namespace, remote, forced: forced, no_tags: no_tags)
+ end
+
def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
@@ -1150,4 +1164,8 @@ class Repository
def repository_event(event, tags = {})
Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags))
end
+
+ def repository_storage_path
+ @project.repository_storage_path
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index e73f7e5d1a3..54550177744 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -25,7 +25,7 @@ class Service < ActiveRecord::Base
belongs_to :project, inverse_of: :services
has_one :service_hook
- validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
+ validates :project_id, presence: true, unless: proc { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 5cc66574941..1e6fc837a75 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,7 +1,7 @@
class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
- commit merge confidentiality status label assignee cross_reference
- title time_tracking branch milestone discussion task moved
+ commit merge confidential visible label assignee cross_reference
+ title time_tracking branch milestone discussion task moved opened closed merged
].freeze
validates :note, presence: true
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 745c2c4b681..a0cb00dba58 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -24,6 +24,10 @@ class BaseService
Gitlab::AppLogger.info message
end
+ def log_error(message)
+ Gitlab::AppLogger.error message
+ end
+
def system_hook_service
SystemHooksService.new
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 2935d00c075..33edcd60944 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -5,8 +5,6 @@ module Ci
def execute(pipeline)
@pipeline = pipeline
- ensure_created_builds! # TODO, remove me in 9.0
-
new_builds =
stage_indexes_of_created_builds.map do |index|
process_stage(index)
@@ -73,18 +71,5 @@ module Ci
def created_builds
pipeline.builds.created
end
-
- # This method is DEPRECATED and should be removed in 9.0.
- #
- # We need it to maintain backwards compatibility with previous versions
- # when builds were not created within one transaction with the pipeline.
- #
- def ensure_created_builds!
- return if created_builds.any?
-
- Ci::CreatePipelineBuildsService
- .new(project, current_user)
- .execute(pipeline)
- end
end
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 940e850600f..8bb995158de 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -3,7 +3,7 @@
#
class NotificationRecipientService
attr_reader :project
-
+
def initialize(project)
@project = project
end
@@ -12,11 +12,7 @@ class NotificationRecipientService
custom_action = build_custom_key(action, target)
recipients = target.participants(current_user)
-
- unless NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
- recipients = add_project_watchers(recipients)
- end
-
+ recipients = add_project_watchers(recipients)
recipients = add_custom_notifications(recipients, custom_action)
recipients = reject_mention_users(recipients)
@@ -43,6 +39,28 @@ class NotificationRecipientService
recipients.uniq
end
+ def build_pipeline_recipients(target, current_user, action:)
+ return [] unless current_user
+
+ custom_action =
+ case action.to_s
+ when 'failed'
+ :failed_pipeline
+ when 'success'
+ :success_pipeline
+ end
+
+ notification_setting = notification_setting_for_user_project(current_user, target.project)
+
+ return [] if notification_setting.mention? || notification_setting.disabled?
+
+ return [] if notification_setting.custom? && !notification_setting.public_send(custom_action)
+
+ return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+
+ reject_users_without_access([current_user], target)
+ end
+
def build_relabeled_recipients(target, current_user, labels:)
recipients = add_labels_subscribers([], target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
@@ -290,4 +308,16 @@ class NotificationRecipientService
def build_custom_key(action, object)
"#{action}_#{object.class.model_name.name.underscore}".to_sym
end
+
+ def notification_setting_for_user_project(user, project)
+ project_setting = user.notification_settings_for(project)
+
+ return project_setting unless project_setting.global?
+
+ group_setting = user.notification_settings_for(project.group)
+
+ return group_setting unless group_setting.global?
+
+ user.global_notification_setting
+ end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 2c6f849259e..6b186263bd1 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -278,11 +278,11 @@ class NotificationService
return unless mailer.respond_to?(email_template)
- recipients ||= NotificationRecipientService.new(pipeline.project).build_recipients(
+ recipients ||= NotificationRecipientService.new(pipeline.project).build_pipeline_recipients(
pipeline,
pipeline.user,
action: pipeline.status,
- skip_current_user: false).map(&:notification_email)
+ ).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index d484a96f785..4c72d5e117d 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -11,7 +11,7 @@ module Projects
success
rescue => e
- error(e.message)
+ error("Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}")
end
private
@@ -32,23 +32,40 @@ module Projects
end
def import_repository
+ raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url)
+
begin
- raise Error, "Blocked import URL." if Gitlab::UrlBlocker.blocked_url?(project.import_url)
- gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
- rescue => e
+ if project.github_import? || project.gitea_import?
+ fetch_repository
+ else
+ clone_repository
+ end
+ rescue Gitlab::Shell::Error => e
# Expire cache to prevent scenarios such as:
# 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true
- project.repository.before_import if project.repository_exists?
+ project.repository.expire_content_cache if project.repository_exists?
- raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
+ raise Error, e.message
end
end
+ def clone_repository
+ gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
+ end
+
+ def fetch_repository
+ project.create_repository
+ project.repository.add_remote(project.import_type, project.import_url)
+ project.repository.set_remote_as_mirror(project.import_type)
+ project.repository.fetch_remote(project.import_type, forced: true)
+ project.repository.remove_remote(project.import_type)
+ end
+
def import_data
return unless has_importer?
- project.repository.before_import unless project.gitlab_project_import?
+ project.repository.expire_content_cache unless project.gitlab_project_import?
unless importer.execute
raise Error, 'The remote data could not be imported.'
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 523b9f41916..17cf71cf098 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -46,6 +46,7 @@ module Projects
end
def error(message, http_status = nil)
+ log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest?
@status.description = message
@status.drop
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index a3c655493a5..c1549df5ac6 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -18,7 +18,11 @@ module Search
end
def scope
- @scope ||= %w[issues merge_requests milestones].delete(params[:scope]) { 'projects' }
+ @scope ||= begin
+ allowed_scopes = %w[issues merge_requests milestones]
+
+ allowed_scopes.delete(params[:scope]) { 'projects' }
+ end
end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index d3e502b66dd..35cfcc3682e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -183,7 +183,9 @@ module SystemNoteService
body = status.dup
body << " via #{source.gfm_reference(project)}" if source
- create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
+ action = status == 'reopened' ? 'opened' : status
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: action))
end
# Called when 'merge when pipeline succeeds' is executed
@@ -273,9 +275,15 @@ module SystemNoteService
#
# Returns the created Note object
def change_issue_confidentiality(issue, project, author)
- body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone'
+ if issue.confidential
+ body = 'made the issue confidential'
+ action = 'confidential'
+ else
+ body = 'made the issue visible to everyone'
+ action = 'visible'
+ end
- create_note(NoteSummary.new(issue, project, author, body, action: 'confidentiality'))
+ create_note(NoteSummary.new(issue, project, author, body, action: action))
end
# Called when a branch in Noteable is changed
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index e7adef5558a..4b344b2edb9 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,29 +1,23 @@
+- action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create'
+
.file-holder.file.append-bottom-default
- .js-file-title.file-title.clearfix
+ .js-file-title.file-title.clearfix{ data: { current_action: action } }
.editor-ref
= icon('code-fork')
= ref
%span.editor-file-name
- if current_action?(:edit) || current_action?(:update)
= text_field_tag 'file_path', (params[:file_path] || @path),
- class: 'form-control new-file-path'
+ class: 'form-control new-file-path js-file-path-name-input'
- if current_action?(:new) || current_action?(:create)
%span.editor-file-name
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name'
+ required: true, class: 'form-control new-file-name js-file-path-name-input'
.pull-right.file-buttons
- .license-selector.js-license-selector-wrap.hidden
- = dropdown_tag("Choose a License template", options: { toggle_class: 'btn js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
- .gitignore-selector.js-gitignore-selector-wrap.hidden
- = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
- .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
- = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
- .dockerfile-selector.js-dockerfile-selector-wrap.hidden
- = dropdown_tag("Choose a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
- = button_tag class: 'soft-wrap-toggle btn', type: 'button' do
+ = button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
No wrap
@@ -31,7 +25,7 @@
= custom_icon('icon_soft_wrap')
Soft wrap
.encoding-selector
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
.file-editor.code
%pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data]
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
new file mode 100644
index 00000000000..d52733d2bd6
--- /dev/null
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -0,0 +1,17 @@
+.template-selectors-menu
+ .templates-selectors-label
+ Template
+ .template-selector-dropdowns-wrap
+ .template-type-selector.js-template-type-selector-wrap.hidden
+ = dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } )
+ .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
+ = dropdown_tag("Apply a License template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
+ = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ .dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
+ = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
+ .template-selectors-undo-menu.hidden
+ %span.text-info Template applied
+ %button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index afe0b5dba45..4b26f944733 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -11,12 +11,15 @@
Someone edited the file the same time you did. Please check out
= link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs.
-
+ .editor-title-row
+ %h3.page-title.blob-edit-page-title
+ Edit file
+ = render 'template_selectors'
.file-editor
%ul.nav-links.no-bottom.js-edit-mode
%li.active
= link_to '#editor' do
- Edit File
+ Write
%li
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 4c449e040ee..2afb909572a 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -2,10 +2,10 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('blob')
-
-%h3.page-title
- New File
-
+.editor-title-row
+ %h3.page-title.blob-new-page-title
+ New file
+ = render 'template_selectors'
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b78de092a60..160345cfaa5 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -238,6 +238,8 @@
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
+ - if @project.deployment_services.any?
+ %li Your deployment services will be broken, you will need to manually fix the services after renaming.
= f.submit 'Rename project', class: "btn btn-warning"
- if can?(current_user, :change_namespace, @project)
%hr
diff --git a/app/views/shared/icons/_activity.svg b/app/views/shared/icons/_activity.svg
deleted file mode 100644
index d465504b154..00000000000
--- a/app/views/shared/icons/_activity.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>path-1</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="_activity" fill="#7E7D7D">
- <g id="Page-1">
- <g id="path-1">
- <path d="M5,0 C4.448,0 4,0.448 4,1 L4,3 L1,3 C0.448,3 0,3.448 0,4 L0,9 C0,9.552 0.448,10 1,10 L5,10 L5,8 L11,8 L11,10 L15,10 C15.552,10 16,9.552 16,9 L16,4 C16,3.448 15.552,3 15,3 L12,3 L12,1 C12,0.448 11.552,0 11,0 L5,0 L5,0 L5,0 L5,0 Z M6,2.5 C6,2.224 6.224,2 6.5,2 L9.5,2 C9.776,2 10,2.224 10,2.5 C10,2.776 9.776,3 9.5,3 L6.5,3 C6.224,3 6,2.776 6,2.5 L6,2.5 L6,2.5 L6,2.5 Z M6,11 L10.001,11 L10.001,9 L6,9 L6,11 L6,11 L6,11 L6,11 Z M11,11 L11,12 L5,12 L5,11 L1,11 C0.448,11 0,11.448 0,12 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,12 C16,11.448 15.552,11 15,11 L11,11 L11,11 L11,11 L11,11 Z"></path>
- </g>
- </g>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_commits.svg b/app/views/shared/icons/_commits.svg
deleted file mode 100644
index ba9bb89935e..00000000000
--- a/app/views/shared/icons/_commits.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>Pasted Image 240</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <path d="M3,8 C3,5.951 4.236,4.194 6,3.422 L6,0 L1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L6,16 L6,12.578 C4.236,11.806 3,10.049 3,8 M7,12.899 L7,16 L9,16 L9,12.899 C8.677,12.965 8.343,13 8,13 C7.657,13 7.323,12.965 7,12.899 M15,0 L10,0 L10,3.422 C11.764,4.194 13,5.951 13,8 C13,10.049 11.764,11.806 10,12.578 L10,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 M10,8 C10,9.105 9.105,10 8,10 C6.895,10 6,9.105 6,8 C6,6.895 6.895,6 8,6 C9.105,6 10,6.895 10,8 M4,8 C4,10.209 5.791,12 8,12 C10.209,12 12,10.209 12,8 C12,5.791 10.209,4 8,4 C5.791,4 4,5.791 4,8 M9,3.101 L9,0 L7,0 L7,3.101 C7.323,3.035 7.657,3 8,3 C8.343,3 8.677,3.035 9,3.101" id="Pasted-Image-240" fill="#7E7D7D"></path>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_contributionanalytics.svg b/app/views/shared/icons/_contributionanalytics.svg
deleted file mode 100644
index adf09a14964..00000000000
--- a/app/views/shared/icons/_contributionanalytics.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group">
- <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1" fill="#7E7C7C"></path>
- <polygon id="Stroke-6" fill="#7E7C7C" points="2.0197351 9.86809696 6.4567351 6.52409696 5.79233671 6.46815759 9.53233671 10.4271576 9.87070552 10.78534 10.2338016 10.4522494 15.0258016 6.05624938 14.3497984 5.31935062 9.55779844 9.71535062 10.2592633 9.74044241 6.51926329 5.78144241 6.21208651 5.45627854 5.8548649 5.72550304 1.4178649 9.06950304"></polygon>
- <path d="M7.0313,6.3928 C7.0313,6.9448 6.5833,7.3928 6.0313,7.3928 C5.4793,7.3928 5.0313,6.9448 5.0313,6.3928 C5.0313,5.8408 5.4793,5.3928 6.0313,5.3928 C6.5833,5.3928 7.0313,5.8408 7.0313,6.3928" id="Fill-8" fill="#FEFEFE"></path>
- <path d="M6.5313,6.3928 C6.5313,6.66865763 6.30715763,6.8928 6.0313,6.8928 C5.75544237,6.8928 5.5313,6.66865763 5.5313,6.3928 C5.5313,6.11694237 5.75544237,5.8928 6.0313,5.8928 C6.30715763,5.8928 6.5313,6.11694237 6.5313,6.3928 L6.5313,6.3928 Z M7.5313,6.3928 C7.5313,5.56465763 6.85944237,4.8928 6.0313,4.8928 C5.20315763,4.8928 4.5313,5.56465763 4.5313,6.3928 C4.5313,7.22094237 5.20315763,7.8928 6.0313,7.8928 C6.85944237,7.8928 7.5313,7.22094237 7.5313,6.3928 L7.5313,6.3928 Z" id="Stroke-10" fill="#7E7C7C"></path>
- <path d="M10.8854,9.8715 C10.8854,10.4235 10.4374,10.8715 9.8854,10.8715 C9.3334,10.8715 8.8854,10.4235 8.8854,9.8715 C8.8854,9.3195 9.3334,8.8715 9.8854,8.8715 C10.4374,8.8715 10.8854,9.3195 10.8854,9.8715" id="Fill-12" fill="#FEFEFE"></path>
- <path d="M10.3854,9.8715 C10.3854,10.1473576 10.1612576,10.3715 9.8854,10.3715 C9.60954237,10.3715 9.3854,10.1473576 9.3854,9.8715 C9.3854,9.59564237 9.60954237,9.3715 9.8854,9.3715 C10.1612576,9.3715 10.3854,9.59564237 10.3854,9.8715 L10.3854,9.8715 Z M11.3854,9.8715 C11.3854,9.04335763 10.7135424,8.3715 9.8854,8.3715 C9.05725763,8.3715 8.3854,9.04335763 8.3854,9.8715 C8.3854,10.6996424 9.05725763,11.3715 9.8854,11.3715 C10.7135424,11.3715 11.3854,10.6996424 11.3854,9.8715 L11.3854,9.8715 Z" id="Stroke-14" fill="#7E7C7C"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_delta.svg b/app/views/shared/icons/_delta.svg
deleted file mode 100644
index 7c0c0d3999c..00000000000
--- a/app/views/shared/icons/_delta.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-<svg width="14px" height="10px" viewBox="322 21 14 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <path d="M330.078605,22.8166945 L335.259532,29.6235062 C335.615145,30.0907182 335.412062,30.4694683 334.822641,30.4694683 L331.657805,30.4694683 L324.04678,30.4694683 C323.449879,30.4694683 323.260751,30.0822112 323.609889,29.6235062 L328.790816,22.8166945 C329.146429,22.3494825 329.729467,22.3579895 330.078605,22.8166945 Z" id="delta" stroke="#5C5C5C" stroke-width="1" fill="none"></path>
-</svg>
diff --git a/app/views/shared/icons/_files.svg b/app/views/shared/icons/_files.svg
deleted file mode 100644
index fc378d81e40..00000000000
--- a/app/views/shared/icons/_files.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>Pasted Image 237</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Pasted-Image-237">
- <path d="M15.1111,16 C15.6021,16 16.0001,15.602 16.0001,15.111 L16.0001,4.444 C15.5341,3.983 12.0671,0.378 11.5551,0 L0.8891,0 C0.3981,0 0.0001,0.398 0.0001,0.889 L0.0001,15.111 C0.0001,15.602 0.3981,16 0.8891,16 L15.1111,16 M14.0001,14.111 L1.8891,14.111 L1.8891,2 L10.8131,2 C11.4451,2.42 13.5811,4.555 14.0001,5.187 L14.0001,14.111" id="Fill-1" fill="#7E7D7D"></path>
- <path d="M0.889,0 C0.398,0 0,0.398 0,0.889 L0,15.111 C0,15.602 0.398,16 0.889,16 L15.111,16 C15.602,16 16,15.602 16,15.111 L16,4.445 C15.534,3.983 12.068,0.377 11.555,0 L0.889,0 L0.889,0 Z M1.889,2 L10.813,2 C11.446,2.42 13.581,4.554 14,5.187 L14,14.111 L1.889,14.111 L1.889,2 L1.889,2 Z" id="Clip-4"></path>
- <polygon id="Fill-6" fill="#7E7D7D" points="9 7 11 7 11 2 9 2"></polygon>
- <polygon id="Clip-9" points="9 7 11 7 11 2.001 9 2.001"></polygon>
- <polygon id="Fill-11" fill="#7E7D7D" points="10 7 15.444 7 15.444 5 10 5"></polygon>
- <polygon id="Clip-14" points="10 7 15.444 7 15.444 5 10 5"></polygon>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_icon_close.svg b/app/views/shared/icons/_icon_close.svg
index 9d62012518b..59a6cb32d18 100644
--- a/app/views/shared/icons/_icon_close.svg
+++ b/app/views/shared/icons/_icon_close.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M9,7.5l5.83-5.91a.48.48,0,0,0,0-.69L14.11.15a.46.46,0,0,0-.68,0l-5.93,6L1.57.15a.46.46,0,0,0-.68,0L.15.9a.48.48,0,0,0,0,.69L6,7.5.15,13.41a.48.48,0,0,0,0,.69l.74.75a.46.46,0,0,0,.68,0l5.93-6,5.93,6a.46.46,0,0,0,.68,0l.74-.75a.48.48,0,0,0,0-.69Z"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M9,7.5l5.83-5.91a.48.48,0,0,0,0-.69L14.11.15a.46.46,0,0,0-.68,0l-5.93,6L1.57.15a.46.46,0,0,0-.68,0L.15.9a.48.48,0,0,0,0,.69L6,7.5.15,13.41a.48.48,0,0,0,0,.69l.74.75a.46.46,0,0,0,.68,0l5.93-6,5.93,6a.46.46,0,0,0,.68,0l.74-.75a.48.48,0,0,0,0-.69Z"/></svg>
diff --git a/app/views/shared/icons/_icon_empty_groups.svg b/app/views/shared/icons/_icon_empty_groups.svg
index 9228be05f03..cf378145e59 100644
--- a/app/views/shared/icons/_icon_empty_groups.svg
+++ b/app/views/shared/icons/_icon_empty_groups.svg
@@ -1 +1 @@
-<svg width="249" height="368" viewBox="891 156 249 368" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="131" height="162" rx="10"/><mask id="e" x="0" y="0" width="131" height="162" fill="#fff"><use xlink:href="#a"/></mask><path d="M223.616 127.958V108.96c0-4.416-3.584-8-8.005-8h-23.985c-2.778 0-5.98 2.014-7.18 4.5l-5.07 10.5h-49.763c-5.527 0-9.996 4.475-9.996 9.997v53.005c0 5.513 4.475 9.997 9.996 9.997h84.01c5.525 0 9.994-4.477 9.994-9.998v-51.004z" id="b"/><mask id="f" x="0" y="0" width="104" height="88" fill="#fff"><use xlink:href="#b"/></mask><path d="M47 25h.996C53.52 25 58 29.472 58 34.99v20.02C58 60.526 53.52 65 47.996 65H10.004C4.48 65 0 60.528 0 55.01V34.99C0 29.474 4.48 25 10.004 25H11v-7c0-9.94 8.06-18 18-18s18 8.06 18 18v7zm-6 0H17v-7c0-6.627 5.373-12 12-12s12 5.373 12 12v7z" id="c"/><mask id="g" x="0" y="0" width="58" height="65" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 10.008C0 4.48 4.476 0 10 0h218c5.523 0 10 4.473 10 10.008v140.94c0 5.53-4.062 11.882-9.08 14.196l-100.84 46.5c-5.015 2.31-13.142 2.312-18.16 0l-100.84-46.5C4.064 162.832 0 156.484 0 150.95V10.007z" id="d"/><mask id="h" x="0" y="0" width="238" height="213.417" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(891 156)"><g transform="rotate(8 -266.528 490.3)"><use stroke="#E5E5E5" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#a"/><rect fill="#FC8A51" x="20" y="31" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="60" y="31" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="36" y="31" width="20" height="4" rx="2"/><rect fill="#6B4FBB" x="20" y="65" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="44" y="65" width="20" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="80" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="80" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="48" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="60" y="80" width="12" height="4" rx="2"/><rect fill="#6B4FBB" x="52" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="68" y="48" width="12" height="4" rx="2"/></g><use stroke="#B5A7DD" mask="url(#f)" stroke-width="8" fill="#FFF" transform="rotate(5 171.616 144.96)" xlink:href="#b"/><path d="M58 132c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#C1E7D0"/><path d="M90.143 132c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M74.686 133.875l-3.18-3.18c-.29-.29-.77-.296-1.06-.005l-1.55 1.55c-.287.287-.29.766.004 1.06l4.92 4.92c.504.504 1.32.504 1.823 0l.654-.653 7.804-7.804c.3-.3.29-.77-.005-1.067l-1.578-1.58c-.302-.3-.775-.298-1.068-.004l-6.764 6.763z" fill="#31AF64"/><path d="M4 66c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18S4 75.94 4 66z" fill="#D5ECF7"/><path d="M36.143 66c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M22 55.714c5.68 0 10.286 4.605 10.286 10.286 0 5.68-4.605 10.286-10.286 10.286-3.45 0-6.505-1.7-8.37-4.307L22 66V55.714z" fill="#2D9FD8"/><g transform="rotate(-8 748.533 18.147)"><use stroke="#FDE5D8" mask="url(#g)" stroke-width="8" fill="#FFF" xlink:href="#c"/><path d="M31 46.584c1.766-.772 3-2.534 3-4.584 0-2.76-2.24-5-5-5s-5 2.24-5 5c0 2.05 1.234 3.812 3 4.584v3.42c0 1.1.895 1.996 2 1.996 1.112 0 2-.894 2-1.997v-3.42z" fill="#FC8A51"/></g><g transform="translate(0 154)"><use stroke="#E5E5E5" mask="url(#h)" stroke-width="8" fill="#FFF" xlink:href="#d"/><g opacity=".3"><path d="M141.837 104.53l-2.56-7.993-5.074-15.843c-.26-.815-1.398-.815-1.66 0l-5.074 15.843h-16.85l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33 22.16-16.33c.61-.452.866-1.25.632-1.98" fill="#A1A1A1"/><path fill="#5C5C5C" d="M119.044 122.84l8.425-26.303h-16.85l8.424 26.304"/><path fill="#787878" d="M119.044 122.84l-8.425-26.303H98.81l20.232 26.304"/><path fill="#787878" d="M119.044 122.84l8.425-26.303h11.807l-20.233 26.304"/><path d="M98.812 96.537l-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33L98.81 96.538z" fill="#A1A1A1"/><path d="M98.812 96.537h11.807l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843z" fill="#5C5C5C"/><path d="M139.277 96.537l2.56 7.993c.234.73-.022 1.528-.634 1.98l-22.16 16.33 20.234-26.303z" fill="#A1A1A1"/><path d="M139.277 96.537H127.47l5.074-15.843c.26-.815 1.398-.815 1.66 0l5.073 15.843z" fill="#5C5C5C"/></g><path d="M57 18.29c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H41c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H77c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm17 24.693c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm202 32.923c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm202 32.923c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm-202 0c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm202 32.922c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm179.023 19.555c-.988.452-1.388 1.55-.894 2.454.493.904 1.694 1.27 2.682.82l14.31-6.545c.99-.452 1.39-1.55.896-2.454-.494-.902-1.696-1.27-2.684-.817l-14.31 6.544zm-32.2 14.723c-.987.452-1.388 1.55-.894 2.454.493.904 1.695 1.27 2.683.818l14.31-6.544c.99-.45 1.39-1.55.895-2.454-.494-.903-1.695-1.27-2.683-.818l-14.31 6.544zm-32.2 14.724c-.987.45-1.387 1.55-.893 2.454.494.903 1.695 1.27 2.683.818l14.31-6.544c.99-.452 1.39-1.55.896-2.454-.495-.904-1.697-1.27-2.685-.818l-14.31 6.544zm-23.67-2.023l-12.186-5.57c-.987-.452-2.19-.086-2.683.817-.494.904-.093 2.003.895 2.454l12.185 5.573c.754.345 1.57.645 2.438.898 1.052.307 2.177-.224 2.513-1.187.335-.962-.246-1.99-1.298-2.298-.677-.197-1.302-.426-1.864-.684zM62.57 168.437c-.988-.452-2.19-.086-2.683.818-.494.903-.094 2.002.894 2.454l14.31 6.544c.988.45 2.19.085 2.683-.818.494-.904.094-2.003-.894-2.454l-14.312-6.544zm-32.2-14.723c-.988-.452-2.19-.086-2.683.818-.494.904-.093 2.003.895 2.454l14.31 6.544c.988.452 2.19.086 2.684-.818.494-.903.093-2.002-.895-2.454l-14.312-6.543z" fill="#EEE"/></g><g><path d="M104 18c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#FADFD9"/><path d="M136.143 18c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M119.43 8.994c0-.707.57-1.28 1.283-1.28h2.574c.71 0 1.284.57 1.284 1.28v10.298c0 .706-.57 1.28-1.283 1.28h-2.574c-.71 0-1.284-.57-1.284-1.28V8.994zm0 15.433c0-.71.57-1.284 1.283-1.284h2.574c.71 0 1.284.57 1.284 1.284V27c0 .71-.57 1.286-1.283 1.286h-2.574c-.71 0-1.284-.57-1.284-1.285v-2.573z" fill="#E75E40"/></g><g><path d="M213 89c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#F6D4DC"/><path d="M245.143 89c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M231 86.348l-3.603-3.602c-.288-.29-.766-.286-1.063.01l-1.578 1.578c-.3.302-.3.773-.01 1.063L228.348 89l-3.602 3.603c-.29.288-.286.766.01 1.063l1.578 1.578c.302.3.773.3 1.063.01L231 91.652l3.603 3.602c.288.29.766.286 1.063-.01l1.578-1.578c.3-.302.3-.773.01-1.063L233.652 89l3.602-3.603c.29-.288.286-.766-.01-1.063l-1.578-1.578c-.302-.3-.773-.3-1.063-.01L231 86.348z" fill="#D22852"/></g></g></svg> \ No newline at end of file
+<svg width="249" height="368" viewBox="891 156 249 368" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="131" height="162" rx="10"/><mask id="e" x="0" y="0" width="131" height="162" fill="#fff"><use xlink:href="#a"/></mask><path d="M223.616 127.958V108.96c0-4.416-3.584-8-8.005-8h-23.985c-2.778 0-5.98 2.014-7.18 4.5l-5.07 10.5h-49.763c-5.527 0-9.996 4.475-9.996 9.997v53.005c0 5.513 4.475 9.997 9.996 9.997h84.01c5.525 0 9.994-4.477 9.994-9.998v-51.004z" id="b"/><mask id="f" x="0" y="0" width="104" height="88" fill="#fff"><use xlink:href="#b"/></mask><path d="M47 25h.996C53.52 25 58 29.472 58 34.99v20.02C58 60.526 53.52 65 47.996 65H10.004C4.48 65 0 60.528 0 55.01V34.99C0 29.474 4.48 25 10.004 25H11v-7c0-9.94 8.06-18 18-18s18 8.06 18 18v7zm-6 0H17v-7c0-6.627 5.373-12 12-12s12 5.373 12 12v7z" id="c"/><mask id="g" x="0" y="0" width="58" height="65" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 10.008C0 4.48 4.476 0 10 0h218c5.523 0 10 4.473 10 10.008v140.94c0 5.53-4.062 11.882-9.08 14.196l-100.84 46.5c-5.015 2.31-13.142 2.312-18.16 0l-100.84-46.5C4.064 162.832 0 156.484 0 150.95V10.007z" id="d"/><mask id="h" x="0" y="0" width="238" height="213.417" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(891 156)"><g transform="rotate(8 -266.528 490.3)"><use stroke="#E5E5E5" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#a"/><rect fill="#FC8A51" x="20" y="31" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="60" y="31" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="36" y="31" width="20" height="4" rx="2"/><rect fill="#6B4FBB" x="20" y="65" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="44" y="65" width="20" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="80" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="80" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="48" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="60" y="80" width="12" height="4" rx="2"/><rect fill="#6B4FBB" x="52" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="68" y="48" width="12" height="4" rx="2"/></g><use stroke="#B5A7DD" mask="url(#f)" stroke-width="8" fill="#FFF" transform="rotate(5 171.616 144.96)" xlink:href="#b"/><path d="M58 132c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#C1E7D0"/><path d="M90.143 132c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M74.686 133.875l-3.18-3.18c-.29-.29-.77-.296-1.06-.005l-1.55 1.55c-.287.287-.29.766.004 1.06l4.92 4.92c.504.504 1.32.504 1.823 0l.654-.653 7.804-7.804c.3-.3.29-.77-.005-1.067l-1.578-1.58c-.302-.3-.775-.298-1.068-.004l-6.764 6.763z" fill="#31AF64"/><path d="M4 66c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18S4 75.94 4 66z" fill="#D5ECF7"/><path d="M36.143 66c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M22 55.714c5.68 0 10.286 4.605 10.286 10.286 0 5.68-4.605 10.286-10.286 10.286-3.45 0-6.505-1.7-8.37-4.307L22 66V55.714z" fill="#2D9FD8"/><g transform="rotate(-8 748.533 18.147)"><use stroke="#FDE5D8" mask="url(#g)" stroke-width="8" fill="#FFF" xlink:href="#c"/><path d="M31 46.584c1.766-.772 3-2.534 3-4.584 0-2.76-2.24-5-5-5s-5 2.24-5 5c0 2.05 1.234 3.812 3 4.584v3.42c0 1.1.895 1.996 2 1.996 1.112 0 2-.894 2-1.997v-3.42z" fill="#FC8A51"/></g><g transform="translate(0 154)"><use stroke="#E5E5E5" mask="url(#h)" stroke-width="8" fill="#FFF" xlink:href="#d"/><g opacity=".3"><path d="M141.837 104.53l-2.56-7.993-5.074-15.843c-.26-.815-1.398-.815-1.66 0l-5.074 15.843h-16.85l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33 22.16-16.33c.61-.452.866-1.25.632-1.98" fill="#A1A1A1"/><path fill="#5C5C5C" d="M119.044 122.84l8.425-26.303h-16.85l8.424 26.304"/><path fill="#787878" d="M119.044 122.84l-8.425-26.303H98.81l20.232 26.304"/><path fill="#787878" d="M119.044 122.84l8.425-26.303h11.807l-20.233 26.304"/><path d="M98.812 96.537l-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33L98.81 96.538z" fill="#A1A1A1"/><path d="M98.812 96.537h11.807l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843z" fill="#5C5C5C"/><path d="M139.277 96.537l2.56 7.993c.234.73-.022 1.528-.634 1.98l-22.16 16.33 20.234-26.303z" fill="#A1A1A1"/><path d="M139.277 96.537H127.47l5.074-15.843c.26-.815 1.398-.815 1.66 0l5.073 15.843z" fill="#5C5C5C"/></g><path d="M57 18.29c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H41c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H77c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm17 24.693c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm202 32.923c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm202 32.923c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm-202 0c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm202 32.922c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm179.023 19.555c-.988.452-1.388 1.55-.894 2.454.493.904 1.694 1.27 2.682.82l14.31-6.545c.99-.452 1.39-1.55.896-2.454-.494-.902-1.696-1.27-2.684-.817l-14.31 6.544zm-32.2 14.723c-.987.452-1.388 1.55-.894 2.454.493.904 1.695 1.27 2.683.818l14.31-6.544c.99-.45 1.39-1.55.895-2.454-.494-.903-1.695-1.27-2.683-.818l-14.31 6.544zm-32.2 14.724c-.987.45-1.387 1.55-.893 2.454.494.903 1.695 1.27 2.683.818l14.31-6.544c.99-.452 1.39-1.55.896-2.454-.495-.904-1.697-1.27-2.685-.818l-14.31 6.544zm-23.67-2.023l-12.186-5.57c-.987-.452-2.19-.086-2.683.817-.494.904-.093 2.003.895 2.454l12.185 5.573c.754.345 1.57.645 2.438.898 1.052.307 2.177-.224 2.513-1.187.335-.962-.246-1.99-1.298-2.298-.677-.197-1.302-.426-1.864-.684zM62.57 168.437c-.988-.452-2.19-.086-2.683.818-.494.903-.094 2.002.894 2.454l14.31 6.544c.988.45 2.19.085 2.683-.818.494-.904.094-2.003-.894-2.454l-14.312-6.544zm-32.2-14.723c-.988-.452-2.19-.086-2.683.818-.494.904-.093 2.003.895 2.454l14.31 6.544c.988.452 2.19.086 2.684-.818.494-.903.093-2.002-.895-2.454l-14.312-6.543z" fill="#EEE"/></g><g><path d="M104 18c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#FADFD9"/><path d="M136.143 18c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M119.43 8.994c0-.707.57-1.28 1.283-1.28h2.574c.71 0 1.284.57 1.284 1.28v10.298c0 .706-.57 1.28-1.283 1.28h-2.574c-.71 0-1.284-.57-1.284-1.28V8.994zm0 15.433c0-.71.57-1.284 1.283-1.284h2.574c.71 0 1.284.57 1.284 1.284V27c0 .71-.57 1.286-1.283 1.286h-2.574c-.71 0-1.284-.57-1.284-1.285v-2.573z" fill="#E75E40"/></g><g><path d="M213 89c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#F6D4DC"/><path d="M245.143 89c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M231 86.348l-3.603-3.602c-.288-.29-.766-.286-1.063.01l-1.578 1.578c-.3.302-.3.773-.01 1.063L228.348 89l-3.602 3.603c-.29.288-.286.766.01 1.063l1.578 1.578c.302.3.773.3 1.063.01L231 91.652l3.603 3.602c.288.29.766.286 1.063-.01l1.578-1.578c.3-.302.3-.773.01-1.063L233.652 89l3.602-3.603c.29-.288.286-.766-.01-1.063l-1.578-1.578c-.302-.3-.773-.3-1.063-.01L231 86.348z" fill="#D22852"/></g></g></svg>
diff --git a/app/views/shared/icons/_icon_mr_issue.svg b/app/views/shared/icons/_icon_mr_issue.svg
index ae219a3ded2..a56af9c556c 100644
--- a/app/views/shared/icons/_icon_mr_issue.svg
+++ b/app/views/shared/icons/_icon_mr_issue.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="m8.411 1.012c-.136-.008-.273-.012-.411-.012-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7 3.866 0 7-3.134 7-7 0-.138-.004-.275-.012-.411-.464.201-.964.334-1.488.386 0 .008 0 .016 0 .025 0 3.038-2.462 5.5-5.5 5.5-3.038 0-5.5-2.462-5.5-5.5 0-3.038 2.462-5.5 5.5-5.5.008 0 .016 0 .025 0 .052-.524.185-1.024.386-1.488"/><path d="m12 2h-1.01c-.54 0-.991.448-.991 1 0 .556.444 1 .991 1h1.01v1.01c0 .54.448.991 1 .991.556 0 1-.444 1-.991v-1.01h1.01c.54 0 .991-.448.991-1 0-.556-.444-1-.991-1h-1.01v-1.01c0-.54-.448-.991-1-.991-.556 0-1 .444-1 .991v1.01m-5 4.01c0-.557.444-1.01 1-1.01.552 0 1 .443 1 1.01v1.981c0 .557-.444 1.01-1 1.01-.552 0-1-.443-1-1.01v-1.981m1 5.991c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="m8.411 1.012c-.136-.008-.273-.012-.411-.012-3.866 0-7 3.134-7 7 0 3.866 3.134 7 7 7 3.866 0 7-3.134 7-7 0-.138-.004-.275-.012-.411-.464.201-.964.334-1.488.386 0 .008 0 .016 0 .025 0 3.038-2.462 5.5-5.5 5.5-3.038 0-5.5-2.462-5.5-5.5 0-3.038 2.462-5.5 5.5-5.5.008 0 .016 0 .025 0 .052-.524.185-1.024.386-1.488"/><path d="m12 2h-1.01c-.54 0-.991.448-.991 1 0 .556.444 1 .991 1h1.01v1.01c0 .54.448.991 1 .991.556 0 1-.444 1-.991v-1.01h1.01c.54 0 .991-.448.991-1 0-.556-.444-1-.991-1h-1.01v-1.01c0-.54-.448-.991-1-.991-.556 0-1 .444-1 .991v1.01m-5 4.01c0-.557.444-1.01 1-1.01.552 0 1 .443 1 1.01v1.981c0 .557-.444 1.01-1 1.01-.552 0-1-.443-1-1.01v-1.981m1 5.991c.552 0 1-.448 1-1 0-.552-.448-1-1-1-.552 0-1 .448-1 1 0 .552.448 1 1 1"/></g></svg>
diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg
index e965afa9a56..4c69fc99a9e 100644
--- a/app/views/shared/icons/_icon_play.svg
+++ b/app/views/shared/icons/_icon_play.svg
@@ -1,3 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play">
- <path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/>
- </svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg>
diff --git a/app/views/shared/icons/_icon_stopwatch.svg b/app/views/shared/icons/_icon_stopwatch.svg
index f20de04538e..6c2a8b2773f 100644
--- a/app/views/shared/icons/_icon_stopwatch.svg
+++ b/app/views/shared/icons/_icon_stopwatch.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14" enable-background="new 0 0 12 14"><path d="m11.5 2.4l-1.3-1.1-1 1.1 1.4 1.1.9-1.1"/><path d="m6.8 2v-.5h.5v-1.5h-2.6v1.5h.5v.5c-2.9.4-5.2 2.9-5.2 6 0 3.3 2.7 6 6 6s6-2.7 6-6c0-3-2.3-5.6-5.2-6m-.8 10.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5"/><path d="m6.2 8.9h-.5c-.1 0-.2-.1-.2-.2v-3.5c0-.1.1-.2.2-.2h.5c.1 0 .2.1.2.2v3.5c0 .1-.1.2-.2.2"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14" enable-background="new 0 0 12 14"><path d="m11.5 2.4l-1.3-1.1-1 1.1 1.4 1.1.9-1.1"/><path d="m6.8 2v-.5h.5v-1.5h-2.6v1.5h.5v.5c-2.9.4-5.2 2.9-5.2 6 0 3.3 2.7 6 6 6s6-2.7 6-6c0-3-2.3-5.6-5.2-6m-.8 10.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5"/><path d="m6.2 8.9h-.5c-.1 0-.2-.1-.2-.2v-3.5c0-.1.1-.2.2-.2h.5c.1 0 .2.1.2.2v3.5c0 .1-.1.2-.2.2"/></svg>
diff --git a/app/views/shared/icons/_icon_timer.svg b/app/views/shared/icons/_icon_timer.svg
index 0b1e5804427..572a31ebcca 100644
--- a/app/views/shared/icons/_icon_timer.svg
+++ b/app/views/shared/icons/_icon_timer.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="#8F8F8F" fill-rule="evenodd"><path d="M29.513 10.134A15.922 15.922 0 0 0 23 7.28V6h2.993C26.55 6 27 5.552 27 5V2a1 1 0 0 0-1.007-1H14.007C13.45 1 13 1.448 13 2v3a1 1 0 0 0 1.007 1H17v1.28C9.597 8.686 4 15.19 4 23c0 8.837 7.163 16 16 16s16-7.163 16-16c0-3.461-1.099-6.665-2.967-9.283l1.327-1.58a2.498 2.498 0 0 0-.303-3.53 2.499 2.499 0 0 0-3.528.315l-1.016 1.212zM20 34c6.075 0 11-4.925 11-11s-4.925-11-11-11S9 16.925 9 23s4.925 11 11 11z"/><path d="M19 21h-4.002c-.552 0-.998.452-.998 1.01v1.98c0 .567.447 1.01.998 1.01h7.004c.274 0 .521-.111.701-.291a.979.979 0 0 0 .297-.704v-8.01c0-.54-.452-.995-1.01-.995h-1.98a.997.997 0 0 0-1.01.995V21z"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="#8F8F8F" fill-rule="evenodd"><path d="M29.513 10.134A15.922 15.922 0 0 0 23 7.28V6h2.993C26.55 6 27 5.552 27 5V2a1 1 0 0 0-1.007-1H14.007C13.45 1 13 1.448 13 2v3a1 1 0 0 0 1.007 1H17v1.28C9.597 8.686 4 15.19 4 23c0 8.837 7.163 16 16 16s16-7.163 16-16c0-3.461-1.099-6.665-2.967-9.283l1.327-1.58a2.498 2.498 0 0 0-.303-3.53 2.499 2.499 0 0 0-3.528.315l-1.016 1.212zM20 34c6.075 0 11-4.925 11-11s-4.925-11-11-11S9 16.925 9 23s4.925 11 11 11z"/><path d="M19 21h-4.002c-.552 0-.998.452-.998 1.01v1.98c0 .567.447 1.01.998 1.01h7.004c.274 0 .521-.111.701-.291a.979.979 0 0 0 .297-.704v-8.01c0-.54-.452-.995-1.01-.995h-1.98a.997.997 0 0 0-1.01.995V21z"/></g></svg>
diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg
index 4f9d9add60d..34f177d7efa 100644
--- a/app/views/shared/icons/_illustration_no_commits.svg
+++ b/app/views/shared/icons/_illustration_no_commits.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="m4.01 2h1.102c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-1.102c-2.218 0-4.01 1.788-4.01 4 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-1.108.892-2 2.01-2m12.702 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m8.088 0c.822 0 1.554.503 1.86 1.254.208.512.791.758 1.303.55.512-.208.758-.791.55-1.303-.609-1.497-2.069-2.5-3.712-2.5h-2.188c-.552 0-1 .448-1 1 0 .552.448 1 1 1h2.188m2.01 12.518c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72c-.552 0-1 .448-1 1 0 .552.448 1 1 1h.72c2.218 0 4.01-1.788 4.01-4v-.382c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.382m-14.325 2c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-8.47 0c-.755 0-1.438-.424-1.782-1.085-.255-.49-.859-.681-1.349-.426-.49.255-.681.859-.426 1.349.684 1.316 2.046 2.162 3.556 2.162h2.57c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-2.57m-2.01-12.136c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-6.664c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.764c0 .552.448 1 1 1 .552 0 1-.448 1-1v-.764" id="0"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="1"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="2"/><path d="m131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9c0-.552-.447-.999-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01c1.655 0 2.996 1.344 2.996 2.999v9c0 1.657-1.35 2.999-2.996 2.999h-22.01c-1.655 0-2.996-1.344-2.996-2.999v-9c0-1.657 1.35-2.999 2.996-2.999" id="3"/><g transform="translate(0 59)"><use xlink:href="#0"/><circle cx="21" cy="24" r="10"/><use xlink:href="#1"/><use xlink:href="#2"/><use xlink:href="#3"/></g></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="m4.01 2h1.102c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-1.102c-2.218 0-4.01 1.788-4.01 4 0 .552.448 1 1 1 .552 0 1-.448 1-1 0-1.108.892-2 2.01-2m12.702 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m11.6 0c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7m8.088 0c.822 0 1.554.503 1.86 1.254.208.512.791.758 1.303.55.512-.208.758-.791.55-1.303-.609-1.497-2.069-2.5-3.712-2.5h-2.188c-.552 0-1 .448-1 1 0 .552.448 1 1 1h2.188m2.01 12.518c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 11.6c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72c-.552 0-1 .448-1 1 0 .552.448 1 1 1h.72c2.218 0 4.01-1.788 4.01-4v-.382c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.382m-14.325 2c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-11.6 0c-.552 0-1 .448-1 1 0 .552.448 1 1 1h5.7c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-5.7m-8.47 0c-.755 0-1.438-.424-1.782-1.085-.255-.49-.859-.681-1.349-.426-.49.255-.681.859-.426 1.349.684 1.316 2.046 2.162 3.556 2.162h2.57c.552 0 1-.448 1-1 0-.552-.448-1-1-1h-2.57m-2.01-12.136c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-11.6c0-.552-.448-1-1-1-.552 0-1 .448-1 1v5.7c0 .552.448 1 1 1 .552 0 1-.448 1-1v-5.7m0-6.664c0-.552-.448-1-1-1-.552 0-1 .448-1 1v.764c0 .552.448 1 1 1 .552 0 1-.448 1-1v-.764" id="0"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="1"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="2"/><path d="m131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9c0-.552-.447-.999-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01c1.655 0 2.996 1.344 2.996 2.999v9c0 1.657-1.35 2.999-2.996 2.999h-22.01c-1.655 0-2.996-1.344-2.996-2.999v-9c0-1.657 1.35-2.999 2.996-2.999" id="3"/><g transform="translate(0 59)"><use xlink:href="#0"/><circle cx="21" cy="24" r="10"/><use xlink:href="#1"/><use xlink:href="#2"/><use xlink:href="#3"/></g></g></svg>
diff --git a/app/views/shared/icons/_members.svg b/app/views/shared/icons/_members.svg
deleted file mode 100644
index f8043b31fe8..00000000000
--- a/app/views/shared/icons/_members.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="22px" height="16px" viewBox="0 0 22 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group" fill="#7E7C7C">
- <path d="M6.4357,11.8588 C7.1487,11.2798 7.8797,10.7808 8.5357,10.3708 C8.5837,10.3008 8.6187,10.2338 8.6187,10.1768 L8.6187,8.8088 C8.9197,8.5218 9.0927,8.1248 9.0927,7.7028 L9.0927,5.3748 C9.0927,3.9478 7.9187,2.7858 6.4757,2.7858 L5.9687,2.7858 C4.5247,2.7858 3.3507,3.9478 3.3507,5.3748 L3.3507,7.7028 C3.3507,8.1248 3.5247,8.5218 3.8247,8.8088 L3.8247,10.5838 C3.2537,10.8738 1.8797,11.6198 0.5967,12.6618 C0.2177,12.9698 -0.0003,13.4258 -0.0003,13.9138 L-0.0003,15.5088 C-0.0003,15.5438 0.0857,15.7668 0.3467,15.7778 C1.3257,15.8198 3.8417,15.8328 5.9617,15.9038 C5.8337,15.8148 5.7447,15.6748 5.7447,15.5088 L5.7447,13.5498 C5.7447,12.9848 5.9967,12.2158 6.4357,11.8588" id="Fill-1"></path>
- <path d="M21.3092,12.1 C19.6932,10.787 17.9592,9.86 17.3042,9.53 L17.3042,7.235 C17.6722,6.9 17.8862,6.428 17.8862,5.925 L17.8862,3.066 C17.8862,1.376 16.4952,0 14.7852,0 L14.1632,0 C12.4532,0 11.0622,1.376 11.0622,3.066 L11.0622,5.925 C11.0622,6.428 11.2752,6.9 11.6442,7.235 L11.6442,9.53 C10.9892,9.86 9.2542,10.787 7.6392,12.1 C7.2002,12.457 6.9482,12.985 6.9482,13.55 L6.9482,15.509 C6.9482,15.78 7.1702,16 7.4442,16 L14.1172,16 L14.1172,11.704 C12.6812,11.595 11.5652,10.853 11.5652,9.945 C11.5652,9.804 11.5982,9.669 11.6482,9.538 C11.9502,10.326 13.0982,10.913 14.4762,10.913 C15.8532,10.913 17.0012,10.326 17.3032,9.538 C17.3532,9.669 17.3862,9.804 17.3862,9.945 C17.3862,10.793 16.4152,11.5 15.1172,11.679 L15.1172,16 L21.5032,16 C21.7772,16 22.0002,15.78 22.0002,15.509 L22.0002,13.55 C22.0002,12.985 21.7482,12.457 21.3092,12.1" id="Fill-4"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_milestones.svg b/app/views/shared/icons/_milestones.svg
deleted file mode 100644
index 3d62ecc0631..00000000000
--- a/app/views/shared/icons/_milestones.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="17px" viewBox="0 0 16 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group" fill="#7E7C7C">
- <path d="M15.1111,1 L0.8891,1 C0.3981,1 0.0001,1.446 0.0001,1.996 L0.0001,15.945 C0.0001,16.495 0.3981,16.941 0.8891,16.941 L15.1111,16.941 C15.6021,16.941 16.0001,16.495 16.0001,15.945 L16.0001,1.996 C16.0001,1.446 15.6021,1 15.1111,1 L15.1111,1 L15.1111,1 Z M14.0001,6.0002 L14.0001,14.949 L2.0001,14.949 L2.0001,6.0002 L14.0001,6.0002 Z M14.0001,4.0002 L14.0001,2.993 L2.0001,2.993 L2.0001,4.0002 L14.0001,4.0002 Z" id="Combined-Shape"></path>
- <polygon id="Fill-11" points="3 2.0002 5 2.0002 5 0.0002 3 0.0002"></polygon>
- <polygon id="Fill-16" points="11 2.0002 13 2.0002 13 0.0002 11 0.0002"></polygon>
- <path d="M5.37709616,11.5511984 L6.92309616,12.7821984 C7.35112915,13.123019 7.97359761,13.0565604 8.32002627,12.6330535 L10.7740263,9.63305349 C11.1237073,9.20557058 11.0606364,8.57555475 10.6331535,8.22587373 C10.2056706,7.87619272 9.57565475,7.93926361 9.22597373,8.36674651 L6.77197373,11.3667465 L8.16890384,11.2176016 L6.62290384,9.98660159 C6.19085236,9.6425813 5.56172188,9.71394467 5.21770159,10.1459962 C4.8736813,10.5780476 4.94504467,11.2071781 5.37709616,11.5511984 L5.37709616,11.5511984 Z" id="Stroke-21"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_mr.svg b/app/views/shared/icons/_mr.svg
deleted file mode 100644
index dd3dbcc4473..00000000000
--- a/app/views/shared/icons/_mr.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group" fill="#7E7C7C">
- <path d="M15.1111,0 L0.8891,0 C0.3981,0 0.0001,0.446 0.0001,0.996 L0.0001,14.945 C0.0001,15.495 0.3981,15.941 0.8891,15.941 L15.1111,15.941 C15.6021,15.941 16.0001,15.495 16.0001,14.945 L16.0001,0.996 C16.0001,0.446 15.6021,0 15.1111,0 L15.1111,0 L15.1111,0 Z M2.0001,13.949 L14.0001,13.949 L14.0001,1.993 L2.0001,1.993 L2.0001,13.949 Z M2,5.0002 L14,5.0002 L14,3.0002 L2,3.0002 L2,5.0002 Z" id="Combined-Shape"></path>
- <path d="M8.547,12.0002 L12,12.0002 L12,10.0002 L8.547,10.0002 L8.547,12.0002 Z M5.2029,12 L3.9999,10.867 L5.2029,9.501 L3.9999,8.181 L5.2029,7 L7.4529,9.499 L5.2029,12 Z" id="Combined-Shape"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_pipelines.svg b/app/views/shared/icons/_pipelines.svg
deleted file mode 100644
index 794e8a27025..00000000000
--- a/app/views/shared/icons/_pipelines.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>Pasted Image 246</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <path d="M12.5,14 C11.672,14 11,13.328 11,12.5 C11,11.672 11.672,11 12.5,11 C13.328,11 14,11.672 14,12.5 C14,13.328 13.328,14 12.5,14 M12.5,9 L3.5,9 C1.567,9 0,10.567 0,12.5 C0,14.433 1.567,16 3.5,16 L12.5,16 C14.433,16 16,14.433 16,12.5 C16,10.567 14.433,9 12.5,9 M3.5,2 C4.328,2 5,2.672 5,3.5 C5,4.328 4.328,5 3.5,5 C2.672,5 2,4.328 2,3.5 C2,2.672 2.672,2 3.5,2 M3.5,7 L12.5,7 C14.433,7 16,5.433 16,3.5 C16,1.567 14.433,0 12.5,0 L3.5,0 C1.567,0 0,1.567 0,3.5 C0,5.433 1.567,7 3.5,7" id="Pasted-Image-246" fill="#303030"></path>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_wiki.svg b/app/views/shared/icons/_wiki.svg
deleted file mode 100644
index 182d91e23aa..00000000000
--- a/app/views/shared/icons/_wiki.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>Pasted Image 241</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <path d="M2.004,12.9999459 L3.939,12.9999459 L3.939,4.99994585 L2.004,4.99994585 L2.004,12.9999459 Z M7.017,9.99994585 L13.018,9.99994585 L13.018,8.99994585 L7.017,8.99994585 L7.017,9.99994585 Z M7.017,7.99994585 L13.018,7.99994585 L13.018,6.99994585 L7.017,6.99994585 L7.017,7.99994585 Z M7.017,5.99994585 L13.018,5.99994585 L13.018,4.99994585 L7.017,4.99994585 L7.017,5.99994585 Z M14.754,-5.41499267e-05 L4.938,-5.41499267e-05 C4.386,-5.41499267e-05 3.938,0.44794585 3.938,0.99994585 L3.938,2.99994585 L1,2.99994585 C0.448,2.99994585 0,3.44794585 0,3.99994585 L0,12.9999459 C0.037,13.4999459 -0.25,16.0509459 3.938,15.9999459 L12.408,15.9999459 C12.408,15.9999459 15.754,15.9169459 15.754,13.9999459 L15.754,0.99994585 C15.754,0.44794585 15.306,-5.41499267e-05 14.754,-5.41499267e-05 L14.754,-5.41499267e-05 Z" id="Pasted-Image-241" fill="#7E7D7D"></path>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index a736bfd91e2..708adbc38f1 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -25,7 +25,7 @@
.form-group
.checkbox{ class: ("prepend-top-0" if index == 0) }
%label{ for: field_id }
- = check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event", checked: notification_setting.events[event])
+ = check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event", checked: notification_setting.public_send(event))
%strong
= notification_event_name(event)
= icon("spinner spin", class: "custom-notification-event-loading")
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index e9a5bd7f24e..2f7967cf531 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -53,6 +53,8 @@ class ProcessCommitWorker
def update_issue_metrics(commit, author)
mentioned_issues = commit.all_references(author).issues
+ return if mentioned_issues.empty?
+
Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil).
update_all(first_mentioned_in_commit_at: commit.committed_date)
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index c8a77e21c12..68a6fd76e70 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -1,6 +1,5 @@
class RepositoryImportWorker
include Sidekiq::Worker
- include Gitlab::ShellAdapter
include DedicatedSidekiqQueue
attr_accessor :project, :current_user
diff --git a/changelogs/unreleased/22303-symbolic-in-tree.yml b/changelogs/unreleased/22303-symbolic-in-tree.yml
new file mode 100644
index 00000000000..02444f571d0
--- /dev/null
+++ b/changelogs/unreleased/22303-symbolic-in-tree.yml
@@ -0,0 +1,4 @@
+---
+title: Fix symlink icon in project tree
+merge_request: 9780
+author: mhasbini
diff --git a/changelogs/unreleased/24187-set-git-terminal-prompt-env-var-in-initializer.yml b/changelogs/unreleased/24187-set-git-terminal-prompt-env-var-in-initializer.yml
new file mode 100644
index 00000000000..7fe5c8a84af
--- /dev/null
+++ b/changelogs/unreleased/24187-set-git-terminal-prompt-env-var-in-initializer.yml
@@ -0,0 +1,4 @@
+---
+title: Set GIT_TERMINAL_PROMPT env variable in initializer
+merge_request: 10372
+author:
diff --git a/changelogs/unreleased/25332-make-file-templates-easy-to-use-and-discover.yml b/changelogs/unreleased/25332-make-file-templates-easy-to-use-and-discover.yml
new file mode 100644
index 00000000000..fc95858f783
--- /dev/null
+++ b/changelogs/unreleased/25332-make-file-templates-easy-to-use-and-discover.yml
@@ -0,0 +1,4 @@
+---
+title: Remove no-new annotation from file_template_mediator.js.
+merge_request: !9782
+author:
diff --git a/changelogs/unreleased/29492-useless-queries.yml b/changelogs/unreleased/29492-useless-queries.yml
new file mode 100644
index 00000000000..266a04be352
--- /dev/null
+++ b/changelogs/unreleased/29492-useless-queries.yml
@@ -0,0 +1,4 @@
+---
+title: Remove useless queries with false conditions (e.g 1=0)
+merge_request: 10141
+author: mhasbini
diff --git a/changelogs/unreleased/29670-jira-integration-documentation-improvment.yml b/changelogs/unreleased/29670-jira-integration-documentation-improvment.yml
new file mode 100644
index 00000000000..8975f0b6ef3
--- /dev/null
+++ b/changelogs/unreleased/29670-jira-integration-documentation-improvment.yml
@@ -0,0 +1,4 @@
+---
+title: Added clarification to the Jira integration documentation.
+merge_request: 10066
+author: Matthew Bender
diff --git a/changelogs/unreleased/backport-sticking-api-helper-changes.yml b/changelogs/unreleased/backport-sticking-api-helper-changes.yml
new file mode 100644
index 00000000000..170ef152271
--- /dev/null
+++ b/changelogs/unreleased/backport-sticking-api-helper-changes.yml
@@ -0,0 +1,4 @@
+---
+title: Backport API changes needed to fix sticking in EE
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-gb-remove-deprecated-pipeline-processing-code.yml b/changelogs/unreleased/fix-gb-remove-deprecated-pipeline-processing-code.yml
new file mode 100644
index 00000000000..32862b527fd
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-remove-deprecated-pipeline-processing-code.yml
@@ -0,0 +1,4 @@
+---
+title: Drop support for correctly processing legacy pipelines
+merge_request: 10266
+author:
diff --git a/changelogs/unreleased/fix-github-importer-slowness.yml b/changelogs/unreleased/fix-github-importer-slowness.yml
new file mode 100644
index 00000000000..c1f8d0e02d5
--- /dev/null
+++ b/changelogs/unreleased/fix-github-importer-slowness.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of GitHub importer for large repositories.
+merge_request: 10273
+author:
diff --git a/changelogs/unreleased/namespace-race-condition.yml b/changelogs/unreleased/namespace-race-condition.yml
new file mode 100644
index 00000000000..2a76b6c74e8
--- /dev/null
+++ b/changelogs/unreleased/namespace-race-condition.yml
@@ -0,0 +1,4 @@
+---
+title: Fix project creation failure due to race condition in namespace directory creation
+merge_request: 10268
+author: Robin Bobbitt
diff --git a/changelogs/unreleased/pages-debug-log.yml b/changelogs/unreleased/pages-debug-log.yml
new file mode 100644
index 00000000000..328c8e4615b
--- /dev/null
+++ b/changelogs/unreleased/pages-debug-log.yml
@@ -0,0 +1,4 @@
+---
+title: Log errors during generating of Gitlab Pages to debug log
+merge_request: 10335
+author: Danilo Bargen
diff --git a/changelogs/unreleased/quiet-pipelines.yml b/changelogs/unreleased/quiet-pipelines.yml
new file mode 100644
index 00000000000..c02eb59b824
--- /dev/null
+++ b/changelogs/unreleased/quiet-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Only email pipeline creators; only email for successful pipelines with custom
+ settings
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove_index_for_users-current_sign_in_at.yml b/changelogs/unreleased/remove_index_for_users-current_sign_in_at.yml
new file mode 100644
index 00000000000..ec3a2c8e2bf
--- /dev/null
+++ b/changelogs/unreleased/remove_index_for_users-current_sign_in_at.yml
@@ -0,0 +1,4 @@
+---
+title: Remove index for users.current sign in at
+merge_request: 10401
+author: blackst0ne
diff --git a/changelogs/unreleased/style-proc-cop.yml b/changelogs/unreleased/style-proc-cop.yml
new file mode 100644
index 00000000000..25acab740bd
--- /dev/null
+++ b/changelogs/unreleased/style-proc-cop.yml
@@ -0,0 +1,4 @@
+---
+title: Enable Style/Proc cop for rubocop
+merge_request:
+author: mhasbini
diff --git a/changelogs/unreleased/zj-kube-service-auto-fill.yml b/changelogs/unreleased/zj-kube-service-auto-fill.yml
new file mode 100644
index 00000000000..7a2c7a5085b
--- /dev/null
+++ b/changelogs/unreleased/zj-kube-service-auto-fill.yml
@@ -0,0 +1,4 @@
+---
+title: Don't fill in the default kubernetes namespace
+merge_request:
+author:
diff --git a/config/application.rb b/config/application.rb
index f9f01b66473..f2ecc4ce77c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -150,6 +150,7 @@ module Gitlab
# This is needed for gitlab-shell
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
+ ENV['GIT_TERMINAL_PROMPT'] = '0'
config.generators do |g|
g.factory_girl false
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index bd27f01c872..4314e902564 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -461,7 +461,7 @@ production: &base
storages: # You must have at least a `default` storage path.
default:
path: /home/git/repositories/
- gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket
+ gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port)
## Backup settings
backup:
diff --git a/config/initializers/8_gitaly.rb b/config/initializers/8_gitaly.rb
index 69c0a91d6f0..c7f27c78535 100644
--- a/config/initializers/8_gitaly.rb
+++ b/config/initializers/8_gitaly.rb
@@ -9,7 +9,7 @@ if Gitlab.config.gitaly.enabled || Rails.env.test?
raise "storage #{name.inspect} is missing a gitaly_address"
end
- unless URI(address).scheme == 'unix'
+ unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect}"
end
diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb
index 95e82966c7a..0ade7109420 100644
--- a/config/initializers/bullet.rb
+++ b/config/initializers/bullet.rb
@@ -1,6 +1,11 @@
-if ENV['ENABLE_BULLET']
- require 'bullet'
+if defined?(Bullet) && ENV['ENABLE_BULLET']
+ Rails.application.configure do
+ config.after_initialize do
+ Bullet.enable = true
- Bullet.enable = true
- Bullet.console = true
+ Bullet.bullet_logger = true
+ Bullet.console = true
+ Bullet.raise = Rails.env.test?
+ end
+ end
end
diff --git a/db/fixtures/development/18_abuse_reports.rb b/db/fixtures/development/18_abuse_reports.rb
index 8618d10387a..88d2f784852 100644
--- a/db/fixtures/development/18_abuse_reports.rb
+++ b/db/fixtures/development/18_abuse_reports.rb
@@ -1,5 +1,27 @@
-require 'factory_girl_rails'
+module Db
+ module Fixtures
+ module Development
+ class AbuseReport
+ def self.seed
+ Gitlab::Seeder.quiet do
+ (::AbuseReport.default_per_page + 3).times do |i|
+ reported_user =
+ ::User.create!(
+ username: "reported_user_#{i}",
+ name: FFaker::Name.name,
+ email: FFaker::Internet.email,
+ confirmed_at: DateTime.now,
+ password: '12345678'
+ )
-(AbuseReport.default_per_page + 3).times do
- FactoryGirl.create(:abuse_report)
+ ::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')
+ print '.'
+ end
+ end
+ end
+ end
+ end
+ end
end
+
+Db::Fixtures::Development::AbuseReport.seed
diff --git a/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb
new file mode 100644
index 00000000000..8316ee9eb9f
--- /dev/null
+++ b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb
@@ -0,0 +1,25 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveIndexForUsersCurrentSignInAt < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if index_exists? :users, :current_sign_in_at
+ if Gitlab::Database.postgresql?
+ execute 'DROP INDEX CONCURRENTLY index_users_on_current_sign_in_at;'
+ else
+ remove_index :users, :current_sign_in_at
+ end
+ end
+ end
+
+ def down
+ add_concurrent_index :users, :current_sign_in_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 19aca4b941e..ccf18d07179 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: 20170329124448) do
+ActiveRecord::Schema.define(version: 20170402231018) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1252,7 +1252,6 @@ ActiveRecord::Schema.define(version: 20170329124448) do
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
- add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree
diff --git a/doc/development/img/cache-hit.svg b/doc/development/img/cache-hit.svg
new file mode 100644
index 00000000000..1c37693df2d
--- /dev/null
+++ b/doc/development/img/cache-hit.svg
@@ -0,0 +1,21 @@
+<svg version="1.1" id="mscgen_js-svg-__svg" class="mscgen_js-svg-__svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="976" height="310" viewBox="0 0 976 310"><desc>
+
+# Generated by mscgen_js - https://sverweij.github.io/mscgen_js
+msc {
+ # options
+ hscale="1.5";
+
+ # entities
+ c [label="Client", textbgcolor="lime"],
+ rails [label="Rails", textbgcolor="cyan"],
+ etag [label="EtagCaching", textbgcolor="orange"],
+ redis [label="Redis", textbgcolor="white"];
+
+ # arcs
+ c =&gt; rails [label="GET /projects/5/pipelines"];
+ rails =&gt; etag [label="GET /projects/5/pipelines"];
+ etag =&gt; redis [label="read(key = 'GET &lt;Etag&gt;')"];
+ redis =&gt; etag [label="cache hit", linecolor="green", textcolor="green"];
+ |||;
+ etag =&gt; c [label="304 Not Modified", linecolor="blue", textcolor="blue"];
+}</desc><defs><style type="text/css">svg.mscgen_js-svg-__svg{font-family:Helvetica,sans-serif;font-size:12px;font-weight:normal;font-style:normal;text-decoration:none;background-color:white;stroke:black;stroke-width:2;color:black}.mscgen_js-svg-__svg path, .mscgen_js-svg-__svg rect{fill:none;color:black;stroke:black}.mscgen_js-svg-__svg .label-text-background{fill:white;stroke:white;stroke-width:0}.mscgen_js-svg-__svg .bglayer{fill:white;stroke:white;stroke-width:0}.mscgen_js-svg-__svg line{stroke:black}.mscgen_js-svg-__svg .return, .mscgen_js-svg-__svg .comment{stroke-dasharray:5,3}.mscgen_js-svg-__svg .inline_expression_divider{stroke-dasharray:10,5}.mscgen_js-svg-__svg text{color:inherit;stroke:none;text-anchor:middle}.mscgen_js-svg-__svg text.entity-text{text-decoration:underline}.mscgen_js-svg-__svg text.anchor-start{text-anchor:start}.mscgen_js-svg-__svg .arrow-marker{overflow:visible}.mscgen_js-svg-__svg .arrow-style{stroke-width:1}.mscgen_js-svg-__svg .arcrow, .mscgen_js-svg-__svg .arcrowomit, .mscgen_js-svg-__svg .emphasised{stroke-linecap:butt}.mscgen_js-svg-__svg .arcrowomit{stroke-dasharray:2,2;}.mscgen_js-svg-__svg .box, .mscgen_js-svg-__svg .entity{fill:white;stroke-linejoin:round}.mscgen_js-svg-__svg .inherit{stroke:inherit;color:inherit}.mscgen_js-svg-__svg .inherit-fill{fill:inherit}.mscgen_js-svg-__svg .watermark{stroke:black;color:black;fill:black;font-size:48pt;font-weight:bold;opacity:0.14}</style><marker orient="auto" id="mscgen_js-svg-__svgmethod-black" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="black" fill="black"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-black" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="black" fill="black"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-blue" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="blue" fill="blue"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-blue" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="blue" fill="blue"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-green" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="green" fill="green"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-green" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="green" fill="green"></polygon></marker></defs><g id="mscgen_js-svg-__svg__body" transform="translate(53,3) scale(1,1)"><g id="mscgen_js-svg-__svg__background"><rect width="976" height="310" x="-53" y="-3" class="bglayer"></rect></g><g id="mscgen_js-svg-__svg__arcspanlayer"></g><g id="mscgen_js-svg-__svg__lifelinelayer"><line x1="75" y1="38" x2="75" y2="76" class="arcrow"></line><line x1="315" y1="38" x2="315" y2="76" class="arcrow"></line><line x1="555" y1="38" x2="555" y2="76" class="arcrow"></line><line x1="795" y1="38" x2="795" y2="76" class="arcrow"></line><line x1="75" y1="76" x2="75" y2="114" class="arcrow"></line><line x1="315" y1="76" x2="315" y2="114" class="arcrow"></line><line x1="555" y1="76" x2="555" y2="114" class="arcrow"></line><line x1="795" y1="76" x2="795" y2="114" class="arcrow"></line><line x1="75" y1="114" x2="75" y2="152" class="arcrow"></line><line x1="315" y1="114" x2="315" y2="152" class="arcrow"></line><line x1="555" y1="114" x2="555" y2="152" class="arcrow"></line><line x1="795" y1="114" x2="795" y2="152" class="arcrow"></line><line x1="75" y1="152" x2="75" y2="190" class="arcrow"></line><line x1="315" y1="152" x2="315" y2="190" class="arcrow"></line><line x1="555" y1="152" x2="555" y2="190" class="arcrow"></line><line x1="795" y1="152" x2="795" y2="190" class="arcrow"></line><line x1="75" y1="190" x2="75" y2="228" class="arcrow"></line><line x1="315" y1="190" x2="315" y2="228" class="arcrow"></line><line x1="555" y1="190" x2="555" y2="228" class="arcrow"></line><line x1="795" y1="190" x2="795" y2="228" class="arcrow"></line><line x1="75" y1="228" x2="75" y2="266" class="arcrow"></line><line x1="315" y1="228" x2="315" y2="266" class="arcrow"></line><line x1="555" y1="228" x2="555" y2="266" class="arcrow"></line><line x1="795" y1="228" x2="795" y2="266" class="arcrow"></line><line x1="75" y1="266" x2="75" y2="304" class="arcrow"></line><line x1="315" y1="266" x2="315" y2="304" class="arcrow"></line><line x1="555" y1="266" x2="555" y2="304" class="arcrow"></line><line x1="795" y1="266" x2="795" y2="304" class="arcrow"></line></g><g id="mscgen_js-svg-__svg__sequencelayer"><g id="mscgen_js-svg-__svgentities"><g><rect width="150" height="38" class="entity" style="fill:lime;"></rect><g><text x="75" y="22.5" class="entity-text "><tspan>Client</tspan></text></g></g><g><rect width="150" height="38" x="240" class="entity" style="fill:cyan;"></rect><g><text x="315" y="22.5" class="entity-text "><tspan>Rails</tspan></text></g></g><g><rect width="150" height="38" x="480" class="entity" style="fill:orange;"></rect><g><text x="555" y="22.5" class="entity-text "><tspan>EtagCaching</tspan></text></g></g><g><rect width="150" height="38" x="720" class="entity" style="fill:white;"></rect><g><text x="795" y="22.5" class="entity-text "><tspan>Redis</tspan></text></g></g></g><g><line x1="75" y1="95" x2="315" y2="95" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="134.06" height="14" x="127.97" y="79.5" class="label-text-background"></rect><text x="195" y="90.5" class="directional-text method-text "><tspan>GET /projects/5/pipelines</tspan></text></g></g><g><line x1="315" y1="133" x2="555" y2="133" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="134.06" height="14" x="367.97" y="117.5" class="label-text-background"></rect><text x="435" y="128.5" class="directional-text method-text "><tspan>GET /projects/5/pipelines</tspan></text></g></g><g><line x1="555" y1="171" x2="795" y2="171" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="135.64" height="14" x="607.17" y="155.5" class="label-text-background"></rect><text x="675" y="166.5" class="directional-text method-text "><tspan>read(key = 'GET &lt;Etag&gt;')</tspan></text></g></g><g><line x1="795" y1="209" x2="555" y2="209" class="arc directional method" style="stroke:green" marker-end="url(#mscgen_js-svg-__svgmethod-green)"></line><g><rect width="48.02" height="14" x="650.98" y="193.5" class="label-text-background"></rect><text x="675" y="204.5" class="directional-text method-text " style="fill:green;"><tspan>cache hit</tspan></text></g></g><g></g><g><line x1="555" y1="285" x2="75" y2="285" class="arc directional method" style="stroke:blue" marker-end="url(#mscgen_js-svg-__svgmethod-blue)"></line><g><rect width="90.72" height="14" x="269.63" y="269.5" class="label-text-background"></rect><text x="315" y="280.5" class="directional-text method-text " style="fill:blue;"><tspan>304 Not Modified</tspan></text></g></g></g><g id="mscgen_js-svg-__svg__notelayer"></g><g id="mscgen_js-svg-__svg__watermark"></g></g></svg> \ No newline at end of file
diff --git a/doc/development/img/cache-miss.svg b/doc/development/img/cache-miss.svg
new file mode 100644
index 00000000000..8429e6a1918
--- /dev/null
+++ b/doc/development/img/cache-miss.svg
@@ -0,0 +1,24 @@
+<svg version="1.1" id="mscgen_js-svg-__svg" class="mscgen_js-svg-__svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="976" height="386" viewBox="0 0 976 386"><desc>
+
+# Generated by mscgen_js - https://sverweij.github.io/mscgen_js
+msc {
+ # options
+ hscale="1.5";
+
+ # entities
+ c [label="Client", textbgcolor="lime"],
+ rails [label="Rails", textbgcolor="cyan"],
+ etag [label="EtagCaching", textbgcolor="orange"],
+ redis [label="Redis", textbgcolor="white"];
+
+ # arcs
+ c =&gt; rails [label="GET /projects/5/pipelines"];
+ rails =&gt; etag [label="GET /projects/5/pipelines"];
+ etag =&gt; redis [label="read(key = 'GET &lt;Etag&gt;')"];
+ redis =&gt; etag [label="cache miss", linecolor="red", textcolor="red"];
+ |||;
+ etag =&gt; redis [label="write('&lt;New Etag&gt;')"];
+ etag =&gt; rails [label="GET /projects/5/pipelines"];
+ rails =&gt; c [label="JSON response w/ ETag", linecolor="blue", textcolor="blue"];
+}
+</desc><defs><style type="text/css">svg.mscgen_js-svg-__svg{font-family:Helvetica,sans-serif;font-size:12px;font-weight:normal;font-style:normal;text-decoration:none;background-color:white;stroke:black;stroke-width:2}.mscgen_js-svg-__svg path, .mscgen_js-svg-__svg rect{fill:none}.mscgen_js-svg-__svg .label-text-background{fill:white;stroke:white;stroke-width:0}.mscgen_js-svg-__svg .bglayer{fill:white;stroke:white;stroke-width:0}.mscgen_js-svg-__svg line{}.mscgen_js-svg-__svg .return, .mscgen_js-svg-__svg .comment{stroke-dasharray:5,3}.mscgen_js-svg-__svg .inline_expression_divider{stroke-dasharray:10,5}.mscgen_js-svg-__svg text{color:inherit;stroke:none;text-anchor:middle}.mscgen_js-svg-__svg text.entity-text{text-decoration:underline}.mscgen_js-svg-__svg text.anchor-start{text-anchor:start}.mscgen_js-svg-__svg .arrow-marker{overflow:visible}.mscgen_js-svg-__svg .arrow-style{stroke-width:1}.mscgen_js-svg-__svg .arcrow, .mscgen_js-svg-__svg .arcrowomit, .mscgen_js-svg-__svg .emphasised{stroke-linecap:butt}.mscgen_js-svg-__svg .arcrowomit{stroke-dasharray:2,2}.mscgen_js-svg-__svg .box, .mscgen_js-svg-__svg .entity{fill:white;stroke-linejoin:round}.mscgen_js-svg-__svg .inherit{stroke:inherit;color:inherit}.mscgen_js-svg-__svg .inherit-fill{fill:inherit}.mscgen_js-svg-__svg .watermark{font-size:48pt;font-weight:bold;opacity:0.14}</style><marker orient="auto" id="mscgen_js-svg-__svgmethod-black" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="black" fill="black"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-black" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="black" fill="black"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-blue" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="blue" fill="blue"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-blue" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="blue" fill="blue"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-red" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="1,1 9,3 1,5" class="arrow-style" stroke="red" fill="red"></polygon></marker><marker orient="auto" id="mscgen_js-svg-__svgmethod-l-red" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10"><polygon points="17,1 9,3 17,5" class="arrow-style" stroke="red" fill="red"></polygon></marker></defs><g id="mscgen_js-svg-__svg__body" transform="translate(53,3) scale(1,1)"><g id="mscgen_js-svg-__svg__background"><rect width="976" height="386" x="-53" y="-3" class="bglayer"></rect></g><g id="mscgen_js-svg-__svg__arcspanlayer"></g><g id="mscgen_js-svg-__svg__lifelinelayer"><line x1="75" y1="38" x2="75" y2="76" class="arcrow"></line><line x1="315" y1="38" x2="315" y2="76" class="arcrow"></line><line x1="555" y1="38" x2="555" y2="76" class="arcrow"></line><line x1="795" y1="38" x2="795" y2="76" class="arcrow"></line><line x1="75" y1="76" x2="75" y2="114" class="arcrow"></line><line x1="315" y1="76" x2="315" y2="114" class="arcrow"></line><line x1="555" y1="76" x2="555" y2="114" class="arcrow"></line><line x1="795" y1="76" x2="795" y2="114" class="arcrow"></line><line x1="75" y1="114" x2="75" y2="152" class="arcrow"></line><line x1="315" y1="114" x2="315" y2="152" class="arcrow"></line><line x1="555" y1="114" x2="555" y2="152" class="arcrow"></line><line x1="795" y1="114" x2="795" y2="152" class="arcrow"></line><line x1="75" y1="152" x2="75" y2="190" class="arcrow"></line><line x1="315" y1="152" x2="315" y2="190" class="arcrow"></line><line x1="555" y1="152" x2="555" y2="190" class="arcrow"></line><line x1="795" y1="152" x2="795" y2="190" class="arcrow"></line><line x1="75" y1="190" x2="75" y2="228" class="arcrow"></line><line x1="315" y1="190" x2="315" y2="228" class="arcrow"></line><line x1="555" y1="190" x2="555" y2="228" class="arcrow"></line><line x1="795" y1="190" x2="795" y2="228" class="arcrow"></line><line x1="75" y1="228" x2="75" y2="266" class="arcrow"></line><line x1="315" y1="228" x2="315" y2="266" class="arcrow"></line><line x1="555" y1="228" x2="555" y2="266" class="arcrow"></line><line x1="795" y1="228" x2="795" y2="266" class="arcrow"></line><line x1="75" y1="266" x2="75" y2="304" class="arcrow"></line><line x1="315" y1="266" x2="315" y2="304" class="arcrow"></line><line x1="555" y1="266" x2="555" y2="304" class="arcrow"></line><line x1="795" y1="266" x2="795" y2="304" class="arcrow"></line><line x1="75" y1="304" x2="75" y2="342" class="arcrow"></line><line x1="315" y1="304" x2="315" y2="342" class="arcrow"></line><line x1="555" y1="304" x2="555" y2="342" class="arcrow"></line><line x1="795" y1="304" x2="795" y2="342" class="arcrow"></line><line x1="75" y1="342" x2="75" y2="380" class="arcrow"></line><line x1="315" y1="342" x2="315" y2="380" class="arcrow"></line><line x1="555" y1="342" x2="555" y2="380" class="arcrow"></line><line x1="795" y1="342" x2="795" y2="380" class="arcrow"></line></g><g id="mscgen_js-svg-__svg__sequencelayer"><g id="mscgen_js-svg-__svgentities"><g><rect width="150" height="38" class="entity" style="fill:lime;"></rect><g><text x="75" y="22.5" class="entity-text "><tspan>Client</tspan></text></g></g><g><rect width="150" height="38" x="240" class="entity" style="fill:cyan;"></rect><g><text x="315" y="22.5" class="entity-text "><tspan>Rails</tspan></text></g></g><g><rect width="150" height="38" x="480" class="entity" style="fill:orange;"></rect><g><text x="555" y="22.5" class="entity-text "><tspan>EtagCaching</tspan></text></g></g><g><rect width="150" height="38" x="720" class="entity" style="fill:white;"></rect><g><text x="795" y="22.5" class="entity-text "><tspan>Redis</tspan></text></g></g></g><g><line x1="75" y1="95" x2="315" y2="95" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="134.06" height="14" x="127.97" y="79.5" class="label-text-background"></rect><text x="195" y="90.5" class="directional-text method-text "><tspan>GET /projects/5/pipelines</tspan></text></g></g><g><line x1="315" y1="133" x2="555" y2="133" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="134.06" height="14" x="367.97" y="117.5" class="label-text-background"></rect><text x="435" y="128.5" class="directional-text method-text "><tspan>GET /projects/5/pipelines</tspan></text></g></g><g><line x1="555" y1="171" x2="795" y2="171" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="135.64" height="14" x="607.17" y="155.5" class="label-text-background"></rect><text x="675" y="166.5" class="directional-text method-text "><tspan>read(key = 'GET &lt;Etag&gt;')</tspan></text></g></g><g><line x1="795" y1="209" x2="555" y2="209" class="arc directional method" style="stroke:red" marker-end="url(#mscgen_js-svg-__svgmethod-red)"></line><g><rect width="60.02" height="14" x="644.98" y="193.5" class="label-text-background"></rect><text x="675" y="204.5" class="directional-text method-text " style="fill:red;"><tspan>cache miss</tspan></text></g></g><g></g><g><line x1="555" y1="285" x2="795" y2="285" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="103.94" height="14" x="623.02" y="269.5" class="label-text-background"></rect><text x="675" y="280.5" class="directional-text method-text "><tspan>write('&lt;New Etag&gt;')</tspan></text></g></g><g><line x1="555" y1="323" x2="315" y2="323" class="arc directional method" style="stroke:black" marker-end="url(#mscgen_js-svg-__svgmethod-black)"></line><g><rect width="134.06" height="14" x="367.97" y="307.5" class="label-text-background"></rect><text x="435" y="318.5" class="directional-text method-text "><tspan>GET /projects/5/pipelines</tspan></text></g></g><g><line x1="315" y1="361" x2="75" y2="361" class="arc directional method" style="stroke:blue" marker-end="url(#mscgen_js-svg-__svgmethod-blue)"></line><g><rect width="130.72" height="14" x="129.63" y="345.5" class="label-text-background"></rect><text x="195" y="356.5" class="directional-text method-text " style="fill:blue;"><tspan>JSON response w/ ETag</tspan></text></g></g></g><g id="mscgen_js-svg-__svg__notelayer"></g><g id="mscgen_js-svg-__svg__watermark"></g></g></svg> \ No newline at end of file
diff --git a/doc/development/polling.md b/doc/development/polling.md
index a7f2962acf0..e5a717f712b 100644
--- a/doc/development/polling.md
+++ b/doc/development/polling.md
@@ -22,6 +22,9 @@ Instead you should use polling mechanism with ETag caching in Redis.
## How it works
+![Cache miss](img/cache-miss.svg)
+![Cache hit](img/cache-hit.svg)
+
1. Whenever a resource changes we generate a random value and store it in
Redis.
1. When a client makes a request we set the `ETag` response header to the value
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 7b586138f42..35586091f74 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -129,6 +129,9 @@ If you want to run the database separately, expect a size of about 1 MB per user
### PostgreSQL Requirements
+As of GitLab 9.0, PostgreSQL 9.6 is recommended. Lower versions of PostgreSQL
+may work but primary testing and developement takes place using PostgreSQL 9.6.
+
Users using PostgreSQL must ensure the `pg_trgm` extension is loaded into every
GitLab database. This extension can be enabled (using a PostgreSQL super user)
by running the following query for every database:
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 154a0f817da..1c493599cf8 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -57,7 +57,7 @@ sudo -u git -H bundle clean
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
-sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production
```
### 4. Update gitlab-workhorse to the corresponding version
diff --git a/doc/user/project/integrations/img/jira_project_settings.png b/doc/user/project/integrations/img/jira_project_settings.png
new file mode 100644
index 00000000000..cb6a6ba14ce
--- /dev/null
+++ b/doc/user/project/integrations/img/jira_project_settings.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 4c64d1e0907..e02f81fd972 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -157,6 +157,11 @@ the same goal:
where `PROJECT-1` is the issue ID of the JIRA project.
+>**Note:**
+- Only commits and merges into the project's default branch (usually **master**) will
+ close an issue in Jira. You can change your projects default branch under
+ [project settings](img/jira_project_settings.png).
+
### JIRA issue closing example
Let's consider the following example:
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 4c52974e103..e91d36987a9 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -66,14 +66,13 @@ Below is the table of events users can be notified of:
In all of the below cases, the notification will be sent to:
- Participants:
- the author and assignee of the issue/merge request
- - the author of the pipeline
- authors of comments on the issue/merge request
- anyone mentioned by `@username` in the issue/merge request title or description
- anyone mentioned by `@username` in any of the comments on the issue/merge request
...with notification level "Participating" or higher
-- Watchers: users with notification level "Watch" (however successful pipeline would be off for watchers)
+- Watchers: users with notification level "Watch"
- Subscribers: anyone who manually subscribed to the issue/merge request
- Custom: Users with notification level "custom" who turned on notifications for any of the events present in the table below
@@ -89,8 +88,8 @@ In all of the below cases, the notification will be sent to:
| Reopen merge request | |
| Merge merge request | |
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
-| Failed pipeline | The above, plus the author of the pipeline |
-| Successful pipeline | The above, plus the author of the pipeline |
+| Failed pipeline | The author of the pipeline |
+| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
In addition, if the title or description of an Issue or Merge Request is
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 37b608ffbd3..0a71833a8a1 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -23,13 +23,13 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
end
step 'I submit new hook' do
- @url = FFaker::Internet.uri("http")
+ @url = 'http://example.org/1'
fill_in "hook_url", with: @url
expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1)
end
step 'I submit new hook with SSL verification enabled' do
- @url = FFaker::Internet.uri("http")
+ @url = 'http://example.org/2'
fill_in "hook_url", with: @url
check "hook_enable_ssl_verification"
expect { click_button "Add Webhook" }.to change(ProjectHook, :count).by(1)
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 1e46b3faf0b..6da8aaac6cb 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -24,8 +24,5 @@ Capybara.ignore_hidden_elements = false
Capybara::Screenshot.prune_strategy = :keep_last_run
Spinach.hooks.before_run do
- require 'spinach/capybara'
- require 'capybara/rails'
-
TestEnv.eager_load_driver_server
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 26cdd9d746d..06c804b1db7 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -5,10 +5,6 @@ ENV['RAILS_ENV'] = 'test'
require './config/environment'
require 'rspec/expectations'
-require_relative 'capybara'
-require_relative 'db_cleaner'
-require_relative 'rerun'
-
if ENV['CI']
require 'knapsack'
Knapsack::Adapters::SpinachAdapter.bind
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 409cb5b924f..9fcf04efa38 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -121,7 +121,7 @@ module API
end
def oauth2_bearer_token_error_handler
- Proc.new do |e|
+ proc do |e|
response =
case e
when MissingTokenError
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 01ae2dbd583..00d44821e3f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -204,7 +204,7 @@ module API
expose :id, :name, :type, :path
expose :mode do |obj, options|
- filemode = obj.mode.to_s(8)
+ filemode = obj.mode
filemode = "0" + filemode if filemode.length < 6
filemode
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 74848a6e144..1369b021ea4 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -50,10 +50,14 @@ module API
forbidden!('Job has been erased!') if job.erased?
end
- def authenticate_job!(job)
+ def authenticate_job!
+ job = Ci::Build.find_by_id(params[:id])
+
validate_job!(job) do
forbidden! unless job_token_valid?(job)
end
+
+ job
end
def job_token_valid?(job)
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 4c9db2c8716..d288369e362 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -113,8 +113,7 @@ module API
optional :state, type: String, desc: %q(Job's status: success, failed)
end
put '/:id' do
- job = Ci::Build.find_by_id(params[:id])
- authenticate_job!(job)
+ job = authenticate_job!
job.update_attributes(trace: params[:trace]) if params[:trace]
@@ -140,8 +139,7 @@ module API
optional :token, type: String, desc: %q(Job's authentication token)
end
patch '/:id/trace' do
- job = Ci::Build.find_by_id(params[:id])
- authenticate_job!(job)
+ job = authenticate_job!
error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range')
content_range = request.headers['Content-Range']
@@ -175,8 +173,7 @@ module API
require_gitlab_workhorse!
Gitlab::Workhorse.verify_api_request!(headers)
- job = Ci::Build.find_by_id(params[:id])
- authenticate_job!(job)
+ job = authenticate_job!
forbidden!('Job is not running') unless job.running?
if params[:filesize]
@@ -212,8 +209,7 @@ module API
not_allowed! unless Gitlab.config.artifacts.enabled
require_gitlab_workhorse!
- job = Ci::Build.find_by_id(params[:id])
- authenticate_job!(job)
+ job = authenticate_job!
forbidden!('Job is not running!') unless job.running?
artifacts_upload_path = ArtifactUploader.artifacts_upload_path
@@ -245,8 +241,7 @@ module API
optional :token, type: String, desc: %q(Job's authentication token)
end
get '/:id/artifacts' do
- job = Ci::Build.find_by_id(params[:id])
- authenticate_job!(job)
+ job = authenticate_job!
artifacts_file = job.artifacts_file
unless artifacts_file.file_storage?
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 746e76a1b1f..95cc6308c3b 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -86,8 +86,7 @@ module Ci
# Example Request:
# PATCH /builds/:id/trace.txt
patch ":id/trace.txt" do
- build = Ci::Build.find_by_id(params[:id])
- authenticate_build!(build)
+ build = authenticate_build!
error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range')
content_range = request.headers['Content-Range']
@@ -117,8 +116,7 @@ module Ci
require_gitlab_workhorse!
Gitlab::Workhorse.verify_api_request!(headers)
not_allowed! unless Gitlab.config.artifacts.enabled
- build = Ci::Build.find_by_id(params[:id])
- authenticate_build!(build)
+ build = authenticate_build!
forbidden!('build is not running') unless build.running?
if params[:filesize]
@@ -154,8 +152,7 @@ module Ci
post ":id/artifacts" do
require_gitlab_workhorse!
not_allowed! unless Gitlab.config.artifacts.enabled
- build = Ci::Build.find_by_id(params[:id])
- authenticate_build!(build)
+ build = authenticate_build!
forbidden!('Build is not running!') unless build.running?
artifacts_upload_path = ArtifactUploader.artifacts_upload_path
@@ -189,8 +186,7 @@ module Ci
# Example Request:
# GET /builds/:id/artifacts
get ":id/artifacts" do
- build = Ci::Build.find_by_id(params[:id])
- authenticate_build!(build)
+ build = authenticate_build!
artifacts_file = build.artifacts_file
unless artifacts_file.file_storage?
@@ -214,8 +210,7 @@ module Ci
# Example Request:
# DELETE /builds/:id/artifacts
delete ":id/artifacts" do
- build = Ci::Build.find_by_id(params[:id])
- authenticate_build!(build)
+ build = authenticate_build!
status(200)
build.erase_artifacts!
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 996990b464f..5109dc9670f 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -13,10 +13,14 @@ module Ci
forbidden! unless current_runner
end
- def authenticate_build!(build)
+ def authenticate_build!
+ build = Ci::Build.find_by_id(params[:id])
+
validate_build!(build) do
forbidden! unless build_token_valid?(build)
end
+
+ build
end
def validate_build!(build)
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index f7450e8b58f..b722d8a9f56 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -33,7 +33,7 @@ module Gitlab
root_id: root_tree.oid,
name: entry[:name],
type: entry[:type],
- mode: entry[:filemode],
+ mode: entry[:filemode].to_s(8),
path: path ? File.join(path, entry[:name]) : entry[:name],
commit_id: sha,
)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index a0dbe0a8c11..fe15fb12adb 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -12,9 +12,11 @@ module Gitlab
end
def self.new_channel(address)
- # NOTE: Gitaly currently runs on a Unix socket, so permissions are
+ address = address.sub(%r{^tcp://}, '') if URI(address).scheme == 'tcp'
+ # NOTE: When Gitaly runs on a Unix socket, permissions are
# handled using the file system and no additional authentication is
# required (therefore the :this_channel_is_insecure flag)
+ # TODO: Add authentication support when Gitaly is running on a TCP socket.
GRPC::Core::Channel.new(address, {}, :this_channel_is_insecure)
end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index eea4a91f17d..a8c0b47e786 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -157,7 +157,7 @@ module Gitlab
end
def restore_source_branch(pull_request)
- project.repository.fetch_ref(repo_url, "pull/#{pull_request.number}/head", pull_request.source_branch_name)
+ project.repository.create_branch(pull_request.source_branch_name, pull_request.source_branch_sha)
end
def restore_target_branch(pull_request)
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index 38660a7ccca..150afa31432 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -20,7 +20,8 @@ module Gitlab
author_id: author_id,
assignee_id: assignee_id,
created_at: raw_data.created_at,
- updated_at: raw_data.updated_at
+ updated_at: raw_data.updated_at,
+ imported: true
}
end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index 28129198438..46deea3cc9f 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -124,9 +124,9 @@ module Gitlab
def name_proc
if allow_username_or_email_login
- Proc.new { |name| name.gsub(/@.*\z/, '') }
+ proc { |name| name.gsub(/@.*\z/, '') }
else
- Proc.new { |name| name }
+ proc { |name| name }
end
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index da8d8ddb8ed..b631ef11ce7 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -88,6 +88,26 @@ module Gitlab
true
end
+ # Fetch remote for repository
+ #
+ # name - project path with namespace
+ # remote - remote name
+ # forced - should we use --force flag?
+ # no_tags - should we use --no-tags flag?
+ #
+ # Ex.
+ # fetch_remote("gitlab/gitlab-ci", "upstream")
+ #
+ def fetch_remote(storage, name, remote, forced: false, no_tags: false)
+ args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, '800']
+ args << '--force' if forced
+ args << '--no-tags' if no_tags
+
+ output, status = Popen.popen(args)
+ raise Error, output unless status.zero?
+ true
+ end
+
# Move repository
# storage - project's storage path
# path - project path with namespace
@@ -174,7 +194,10 @@ module Gitlab
# add_namespace("/path/to/storage", "gitlab")
#
def add_namespace(storage, name)
- FileUtils.mkdir_p(full_path(storage, name), mode: 0770) unless exists?(storage, name)
+ path = full_path(storage, name)
+ FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name)
+ rescue Errno::EEXIST => e
+ Rails.logger.warn("Directory exists as a file: #{e} at: #{path}")
end
# Remove directory from repositories storage
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index d1131ad65e0..09c3bd8fc9e 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -16,7 +16,7 @@ module Gitlab
SECRET_LENGTH = 32
class << self
- def git_http_ok(repository, user)
+ def git_http_ok(repository, user, action)
repo_path = repository.path_to_repo
params = {
GL_ID: Gitlab::GlId.gl_id(user),
@@ -26,13 +26,26 @@ module Gitlab
if Gitlab.config.gitaly.enabled
storage = repository.project.repository_storage
address = Gitlab::GitalyClient.get_address(storage)
- params[:GitalySocketPath] = URI(address).path
# TODO: use GitalyClient code to assemble the Repository message
params[:Repository] = Gitaly::Repository.new(
path: repo_path,
storage_name: storage,
relative_path: Gitlab::RepoPath.strip_storage_path(repo_path),
).to_h
+
+ feature_enabled = case action.to_s
+ when 'git_receive_pack'
+ # Disabled for now, see https://gitlab.com/gitlab-org/gitaly/issues/172
+ false
+ when 'git_upload_pack'
+ Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
+ when 'info_refs'
+ true
+ else
+ raise "Unsupported action: #{action}"
+ end
+
+ params[:GitalySocketPath] = URI(address).path if feature_enabled
end
params
diff --git a/spec/controllers/profiles/personal_access_tokens_spec.rb b/spec/controllers/profiles/personal_access_tokens_spec.rb
index dfed1de2046..98a43e278b2 100644
--- a/spec/controllers/profiles/personal_access_tokens_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_spec.rb
@@ -12,7 +12,7 @@ describe Profiles::PersonalAccessTokensController do
end
it "allows creation of a token with scopes" do
- name = FFaker::Product.brand
+ name = 'My PAT'
scopes = %w[api read_user]
post :create, personal_access_token: token_attributes.merge(scopes: scopes, name: name)
diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb
index 24225468d55..9a0be1a4598 100644
--- a/spec/factories/chat_names.rb
+++ b/spec/factories/chat_names.rb
@@ -6,11 +6,7 @@ FactoryGirl.define do
team_id 'T0001'
team_domain 'Awesome Team'
- sequence :chat_id do |n|
- "U#{n}"
- end
- sequence :chat_name do |n|
- "user#{n}"
- end
+ sequence(:chat_id) { |n| "U#{n}" }
+ chat_name { generate(:username) }
end
end
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
index 82f44fa3d15..ffedf69a69b 100644
--- a/spec/factories/chat_teams.rb
+++ b/spec/factories/chat_teams.rb
@@ -1,9 +1,6 @@
FactoryGirl.define do
factory :chat_team, class: ChatTeam do
- sequence :team_id do |n|
- "abcdefghijklm#{n}"
- end
-
+ sequence(:team_id) { |n| "abcdefghijklm#{n}" }
namespace factory: :group
end
end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index b67c96bc00d..561fbc8e247 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -48,6 +48,10 @@ FactoryGirl.define do
trait :success do
status :success
end
+
+ trait :failed do
+ status :failed
+ end
end
end
end
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index c3b4aff55ba..05abf60d5ce 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,8 +1,6 @@
FactoryGirl.define do
factory :ci_runner, class: Ci::Runner do
- sequence :description do |n|
- "My runner#{n}"
- end
+ sequence(:description) { |n| "My runner#{n}" }
platform "darwin"
is_shared false
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index 9794772ac7d..8303861bcfe 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :email do
user
- email { FFaker::Internet.email('alias') }
+ email { generate(:email_alias) }
end
end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 7e09f1ba8ea..0b6977e3b17 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,10 +1,6 @@
FactoryGirl.define do
- sequence :issue_created_at do |n|
- 4.hours.ago + ( 2 * n ).seconds
- end
-
factory :issue do
- title
+ title { generate(:title) }
author
project factory: :empty_project
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 5ba8443c62c..22c2a1f15e2 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,7 +1,10 @@
FactoryGirl.define do
- factory :label, class: ProjectLabel do
- sequence(:title) { |n| "label#{n}" }
+ trait :base_label do
+ title { generate(:label_title) }
color "#990000"
+ end
+
+ factory :label, traits: [:base_label], class: ProjectLabel do
project factory: :empty_project
transient do
@@ -15,9 +18,7 @@ FactoryGirl.define do
end
end
- factory :group_label, class: GroupLabel do
- sequence(:title) { |n| "label#{n}" }
- color "#990000"
+ factory :group_label, traits: [:base_label] do
group
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index ae0bbbd6aeb..e36fe326e1c 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :merge_request do
- title
+ title { generate(:title) }
author
association :source_project, :repository, factory: :project
target_project { source_project }
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index 86cdc208268..c7ede40f240 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,8 +1,8 @@
FactoryGirl.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
- name { FFaker::Name.name }
+ sequence(:name) { |n| "OAuth App #{n}" }
uid { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
- redirect_uri { FFaker::Internet.uri('http') }
+ redirect_uri { generate(:url) }
owner
owner_type 'User'
end
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index 7b15ba47de1..06acaff6cd0 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
factory :personal_access_token do
user
token { SecureRandom.hex(50) }
- name { FFaker::Product.brand }
+ sequence(:name) { |n| "PAT #{n}" }
revoked false
expires_at { 5.days.from_now }
scopes ['api']
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 424ecc65759..39c2a9dd1fb 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :project_hook do
- url { FFaker::Internet.uri('http') }
+ url { generate(:url) }
trait :token do
token { SecureRandom.hex(10) }
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
new file mode 100644
index 00000000000..c0232ba5bf6
--- /dev/null
+++ b/spec/factories/sequences.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ sequence(:username) { |n| "user#{n}" }
+ sequence(:name) { |n| "John Doe#{n}" }
+ sequence(:email) { |n| "user#{n}@example.org" }
+ sequence(:email_alias) { |n| "user.alias#{n}@example.org" }
+ sequence(:title) { |n| "My title #{n}" }
+ sequence(:filename) { |n| "filename-#{n}.rb" }
+ sequence(:url) { |n| "http://example#{n}.org" }
+ sequence(:label_title) { |n| "label#{n}" }
+ sequence(:branch) { |n| "my-branch-#{n}" }
+ sequence(:past_time) { |n| 4.hours.ago + (2 * n).seconds }
+end
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
index 6dd6af63f3e..e3f88ab8fcc 100644
--- a/spec/factories/service_hooks.rb
+++ b/spec/factories/service_hooks.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :service_hook do
- url { FFaker::Internet.uri('http') }
+ url { generate(:url) }
service
end
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 365f12a0c95..18cb0f5de26 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -1,17 +1,9 @@
FactoryGirl.define do
- sequence :title, aliases: [:content] do
- FFaker::Lorem.sentence
- end
-
- sequence :file_name do
- FFaker::Internet.user_name
- end
-
factory :snippet do
author
- title
- content
- file_name
+ title { generate(:title) }
+ content { generate(:title) }
+ file_name { generate(:filename) }
trait :public do
visibility_level Snippet::PUBLIC
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
index a4f6d291269..e369f9f13e9 100644
--- a/spec/factories/spam_logs.rb
+++ b/spec/factories/spam_logs.rb
@@ -1,9 +1,9 @@
FactoryGirl.define do
factory :spam_log do
user
- source_ip { FFaker::Internet.ip_v4_address }
+ sequence(:source_ip) { |n| "42.42.42.#{n % 255}" }
noteable_type 'Issue'
- title { FFaker::Lorem.sentence }
- description { FFaker::Lorem.paragraph(5) }
+ sequence(:title) { |n| "Spam title #{n}" }
+ description { "Spam description\nwith\nmultiple\nlines" }
end
end
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
index c786e9cb79b..841e1e293e8 100644
--- a/spec/factories/system_hooks.rb
+++ b/spec/factories/system_hooks.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
factory :system_hook do
- url { FFaker::Internet.uri('http') }
+ url { generate(:url) }
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 249dabbaae8..e1ae94a08e4 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,10 +1,8 @@
FactoryGirl.define do
- sequence(:name) { FFaker::Name.name }
-
factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator, :resource_owner] do
- email { FFaker::Internet.email }
- name
- sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
+ email { generate(:email) }
+ name { generate(:name) }
+ username { generate(:username) }
password "12345678"
confirmed_at { Time.now }
confirmation_token { nil }
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index 562ace92598..bee57472270 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'Admin browse spam logs' do
- let!(:spam_log) { create(:spam_log) }
+ let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) }
before do
login_as :admin
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index f246997d5a2..570c374a89b 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -26,7 +26,7 @@ describe "Admin::Hooks", feature: true do
end
describe "New Hook" do
- let(:url) { FFaker::Internet.uri('http') }
+ let(:url) { generate(:url) }
it 'adds new hook' do
visit admin_hooks_path
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 9ff5c2f9d40..ff23d486355 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
describe "token creation" do
it "allows creation of a token" do
- name = FFaker::Product.brand
+ name = 'Hello World'
visit admin_user_impersonation_tokens_path(user_id: user.username)
fill_in "Name", with: name
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index ea7a97d1d4f..009e9c6b04c 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -42,7 +42,7 @@ describe 'Auto deploy' do
it 'includes OpenShift as an available template', js: true do
click_link 'Set up auto deploy'
- click_button 'Choose a GitLab CI Yaml template'
+ click_button 'Apply a GitLab CI Yaml template'
within '.gitlab-ci-yml-selector' do
expect(page).to have_content('OpenShift')
@@ -51,7 +51,7 @@ describe 'Auto deploy' do
it 'creates a merge request using "auto-deploy" branch', js: true do
click_link 'Set up auto deploy'
- click_button 'Choose a GitLab CI Yaml template'
+ click_button 'Apply a GitLab CI Yaml template'
within '.gitlab-ci-yml-selector' do
click_on 'OpenShift'
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index b90bf6268fd..3dc872ae520 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -46,16 +46,16 @@ describe 'issuable list', feature: true do
end
def create_issuables(issuable_type)
- 3.times do
+ 3.times do |n|
issuable =
if issuable_type == :issue
create(:issue, project: project, author: user)
else
- create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ create(:merge_request, source_project: project, source_branch: generate(:branch))
end
2.times do
- create(:note_on_issue, noteable: issuable, project: project, note: 'Test note')
+ create(:note_on_issue, noteable: issuable, project: project)
end
create(:award_emoji, :downvote, awardable: issuable)
@@ -65,9 +65,8 @@ describe 'issuable list', feature: true do
if issuable_type == :issue
issue = Issue.reorder(:iid).first
merge_request = create(:merge_request,
- title: FFaker::Lorem.sentence,
source_project: project,
- source_branch: FFaker::Name.name)
+ source_branch: generate(:branch))
MergeRequestsClosingIssues.create!(issue: issue, merge_request: merge_request)
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index f463312bf57..004e335dd38 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -6,8 +6,8 @@ describe 'Filter issues', js: true, feature: true do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
- let!(:user) { create(:user) }
- let!(:user2) { create(:user) }
+ let!(:user) { create(:user, username: 'joe') }
+ let!(:user2) { create(:user, username: 'jane') }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
@@ -70,7 +70,7 @@ describe 'Filter issues', js: true, feature: true do
issue_with_caps_label.labels << caps_sensitive_label
issue_with_everything = create(:issue,
- title: "Bug report with everything you thought was possible",
+ title: "Bug report foo was possible",
project: project,
milestone: milestone,
author: user,
@@ -687,10 +687,10 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by searched text, author, more text, assignee and even more text' do
- input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with")
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} foo")
expect_issues_list_count(1)
- expect_filtered_search_input('bug report with')
+ expect_filtered_search_input('bug report foo')
end
it 'filters issues by searched text, author, assignee and label' do
@@ -701,10 +701,10 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by searched text, author, text, assignee, text, label and text' do
- input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything")
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} report label:~#{bug_label.title} foo")
expect_issues_list_count(1)
- expect_filtered_search_input('bug report with everything')
+ expect_filtered_search_input('bug report foo')
end
it 'filters issues by searched text, author, assignee, label and milestone' do
@@ -715,10 +715,10 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by searched text, author, text, assignee, text, label, text, milestone and text' do
- input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything milestone:%#{milestone.title} you")
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} report label:~#{bug_label.title} milestone:%#{milestone.title} foo")
expect_issues_list_count(1)
- expect_filtered_search_input('bug report with everything you')
+ expect_filtered_search_input('bug report foo')
end
it 'filters issues by searched text, author, assignee, multiple labels and milestone' do
@@ -729,10 +729,10 @@ describe 'Filter issues', js: true, feature: true do
end
it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do
- input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything label:~#{caps_sensitive_label.title} you milestone:%#{milestone.title} thought")
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} report label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} foo")
expect_issues_list_count(1)
- expect_filtered_search_input('bug report with everything you thought')
+ expect_filtered_search_input('bug report foo')
end
end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 0917d4dc3ef..99fba594651 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -27,7 +27,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
describe "token creation" do
it "allows creation of a personal access token" do
- name = FFaker::Product.brand
+ name = 'My PAT'
visit profile_personal_access_tokens_path
fill_in "Name", with: name
@@ -52,7 +52,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
it "displays an error message" do
disallow_personal_access_token_saves!
visit profile_personal_access_tokens_path
- fill_in "Name", with: FFaker::Product.brand
+ fill_in "Name", with: 'My PAT'
expect { click_on "Create Personal Access Token" }.not_to change { PersonalAccessToken.count }
expect(page).to have_content("Name cannot be nil")
diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb
index 5686868a0c4..d214a531138 100644
--- a/spec/features/projects/blobs/user_create_spec.rb
+++ b/spec/features/projects/blobs/user_create_spec.rb
@@ -88,7 +88,7 @@ feature 'New blob creation', feature: true, js: true do
scenario 'shows error message' do
expect(page).to have_content('Your changes could not be committed because a file with the same name already exists')
- expect(page).to have_content('New File')
+ expect(page).to have_content('New file')
expect(page).to have_content('NextFeature')
end
end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index ccadc936567..6b281e6d21d 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -40,7 +40,7 @@ feature 'project owner creates a license file', feature: true, js: true do
scenario 'project master creates a license file from the "Add license" link' do
click_link 'Add License'
- expect(page).to have_content('New File')
+ expect(page).to have_content('New file')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
@@ -63,7 +63,7 @@ feature 'project owner creates a license file', feature: true, js: true do
def select_template(template)
page.within('.js-license-selector-wrap') do
- click_button 'Choose a License template'
+ click_button 'Apply a License template'
click_link template
wait_for_ajax
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 420db962318..87322ac2584 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -14,7 +14,7 @@ feature 'project owner sees a link to create a license file in empty project', f
visit namespace_project_path(project.namespace, project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
- expect(page).to have_content('New File')
+ expect(page).to have_content('New file')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
@@ -40,7 +40,7 @@ feature 'project owner sees a link to create a license file in empty project', f
def select_template(template)
page.within('.js-license-selector-wrap') do
- click_button 'Choose a License template'
+ click_button 'Apply a License template'
click_link template
wait_for_ajax
end
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
new file mode 100644
index 00000000000..5ee5e5b4c4e
--- /dev/null
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -0,0 +1,135 @@
+require 'spec_helper'
+
+feature 'Template type dropdown selector', js: true do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_as user
+ end
+
+ context 'editing a non-matching file' do
+ before do
+ create_and_edit_file('.random-file.js')
+ end
+
+ scenario 'not displayed' do
+ check_type_selector_display(false)
+ end
+
+ scenario 'selects every template type correctly' do
+ fill_in 'file_path', with: '.gitignore'
+ try_selecting_all_types
+ end
+
+ scenario 'updates toggle value when input matches' do
+ fill_in 'file_path', with: '.gitignore'
+ check_type_selector_toggle_text('.gitignore')
+ end
+ end
+
+ context 'editing a matching file' do
+ before do
+ visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, 'LICENSE'))
+ end
+
+ scenario 'displayed' do
+ check_type_selector_display(true)
+ end
+
+ scenario 'is displayed when input matches' do
+ check_type_selector_display(true)
+ end
+
+ scenario 'selects every template type correctly' do
+ try_selecting_all_types
+ end
+
+ context 'user previews changes' do
+ before do
+ click_link 'Preview Changes'
+ end
+
+ scenario 'type selector is hidden and shown correctly' do
+ check_type_selector_display(false)
+ click_link 'Write'
+ check_type_selector_display(true)
+ end
+ end
+ end
+
+ context 'creating a matching file' do
+ before do
+ visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ end
+
+ scenario 'is displayed' do
+ check_type_selector_display(true)
+ end
+
+ scenario 'toggle is set to the correct value' do
+ check_type_selector_toggle_text('.gitignore')
+ end
+
+ scenario 'selects every template type correctly' do
+ try_selecting_all_types
+ end
+ end
+
+ context 'creating a file' do
+ before do
+ visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+ end
+
+ scenario 'type selector is shown' do
+ check_type_selector_display(true)
+ end
+
+ scenario 'toggle is set to the proper value' do
+ check_type_selector_toggle_text('Choose type')
+ end
+
+ scenario 'selects every template type correctly' do
+ try_selecting_all_types
+ end
+ end
+end
+
+def check_type_selector_display(is_visible)
+ count = is_visible ? 1 : 0
+ expect(page).to have_css('.js-template-type-selector', count: count)
+end
+
+def try_selecting_all_types
+ try_selecting_template_type('LICENSE', 'Apply a License template')
+ try_selecting_template_type('Dockerfile', 'Apply a Dockerfile template')
+ try_selecting_template_type('.gitlab-ci.yml', 'Apply a GitLab CI Yaml template')
+ try_selecting_template_type('.gitignore', 'Apply a .gitignore template')
+end
+
+def try_selecting_template_type(template_type, selector_label)
+ select_template_type(template_type)
+ check_template_selector_display(selector_label)
+ check_type_selector_toggle_text(template_type)
+end
+
+def select_template_type(template_type)
+ find('.js-template-type-selector').click
+ find('.dropdown-content li', text: template_type).click
+end
+
+def check_template_selector_display(content)
+ expect(page).to have_content(content)
+end
+
+def check_type_selector_toggle_text(template_type)
+ dropdown_toggle_button = find('.template-type-selector .dropdown-toggle-text')
+ expect(dropdown_toggle_button).to have_content(template_type)
+end
+
+def create_and_edit_file(file_name)
+ visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: file_name)
+ click_button "Commit Changes"
+ visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, file_name))
+end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
new file mode 100644
index 00000000000..5479ea34610
--- /dev/null
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+include WaitForAjax
+
+feature 'Template Undo Button', js: true do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_as user
+ end
+
+ context 'editing a matching file and applying a template' do
+ before do
+ visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, "LICENSE"))
+ select_file_template('.js-license-selector', 'Apache License 2.0')
+ end
+
+ scenario 'reverts template application' do
+ try_template_undo('http://www.apache.org/licenses/', 'Apply a License template')
+ end
+ end
+
+ context 'creating a non-matching file' do
+ before do
+ visit namespace_project_new_blob_path(project.namespace, project, 'master')
+ select_file_template_type('LICENSE')
+ select_file_template('.js-license-selector', 'Apache License 2.0')
+ end
+
+ scenario 'reverts template application' do
+ try_template_undo('http://www.apache.org/licenses/', 'Apply a License template')
+ end
+ end
+end
+
+def try_template_undo(template_content, toggle_text)
+ check_undo_button_display
+ check_content_reverted(template_content)
+ check_toggle_text_set(toggle_text)
+end
+
+def check_toggle_text_set(neutral_toggle_text)
+ expect(page).to have_content(neutral_toggle_text)
+end
+
+def check_undo_button_display
+ expect(page).to have_content('Template applied')
+ expect(page).to have_css('.template-selectors-undo-menu .btn-info')
+end
+
+def check_content_reverted(template_content)
+ find('.template-selectors-undo-menu .btn-info').click
+ expect(page).not_to have_content(template_content)
+ expect(find('.template-type-selector .dropdown-toggle-text')).to have_content()
+end
+
+def select_file_template(template_selector_selector, template_name)
+ find(template_selector_selector).click
+ find('.dropdown-content li', text: template_name).click
+ wait_for_ajax
+end
+
+def select_file_template_type(template_type)
+ find('.js-template-type-selector').click
+ find('.dropdown-content li', text: template_type).click
+end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index a8d00bb8e5a..28373098123 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
+feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
include WaitForAjax
before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) }
@@ -11,8 +11,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
wait_for_ajax
end
- def register_u2f_device(u2f_device = nil)
- name = FFaker::Name.first_name
+ def register_u2f_device(u2f_device = nil, name: 'My device')
u2f_device ||= FakeU2fDevice.new(page, name)
u2f_device.respond_to_u2f_registration
click_on 'Setup New U2F Device'
@@ -62,7 +61,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page).to have_content('Your U2F device was registered')
# Second device
- second_device = register_u2f_device
+ second_device = register_u2f_device(name: 'My other device')
expect(page).to have_content('Your U2F device was registered')
expect(page).to have_content(first_device.name)
@@ -76,7 +75,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page).to have_content("You've already enabled two-factor authentication using mobile")
first_u2f_device = register_u2f_device
- second_u2f_device = register_u2f_device
+ second_u2f_device = register_u2f_device(name: 'My other device')
click_on "Delete", match: :first
@@ -99,7 +98,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
manage_two_factor_authentication
- register_u2f_device(u2f_device)
+ register_u2f_device(u2f_device, name: 'My other device')
expect(page).to have_content('Your U2F device was registered')
expect(U2fRegistration.count).to eq(2)
@@ -198,7 +197,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
manage_two_factor_authentication
- register_u2f_device
+ register_u2f_device(name: 'My other device')
logout
# Try authenticating user with the old U2F device
@@ -231,7 +230,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
describe "when a given U2F device has not been registered" do
it "does not allow logging in with that particular device" do
- unregistered_device = FakeU2fDevice.new(page, FFaker::Name.first_name)
+ unregistered_device = FakeU2fDevice.new(page, 'My device')
login_as(user)
unregistered_device.respond_to_u2f_authentication
expect(page).to have_content('We heard back from your U2F device')
@@ -252,7 +251,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
# Register second device
visit profile_two_factor_auth_path
expect(page).to have_content("Your U2F device needs to be set up.")
- second_device = register_u2f_device
+ second_device = register_u2f_device(name: 'My other device')
logout
# Authenticate as both devices
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index ff8b8daa347..70a18f31744 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -56,7 +56,7 @@ describe 'trusted_proxies', lib: true do
end
def stub_request(headers = {})
- ActionDispatch::RemoteIp.new(Proc.new { }, false, Rails.application.config.action_dispatch.trusted_proxies).call(headers)
+ ActionDispatch::RemoteIp.new(proc { }, false, Rails.application.config.action_dispatch.trusted_proxies).call(headers)
ActionDispatch::Request.new(headers)
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 83d2ff8f9b3..82685712b5b 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -19,6 +19,7 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(dir.name).to eq('encoding') }
it { expect(dir.path).to eq('encoding') }
+ it { expect(dir.mode).to eq('40000') }
context :subdir do
let(:subdir) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files').first }
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 48f7754bed8..703b41f95ac 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -183,7 +183,7 @@ describe Gitlab::GitAccess, lib: true do
describe '#check_push_access!' do
before { merge_into_protected_branch }
- let(:unprotected_branch) { FFaker::Internet.user_name }
+ let(:unprotected_branch) { 'unprotected_branch' }
let(:changes) do
{ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
@@ -211,9 +211,9 @@ describe Gitlab::GitAccess, lib: true do
target_branch = project.repository.lookup('feature')
source_branch = project.repository.create_file(
user,
- FFaker::InternetSE.login_user_name,
- FFaker::HipsterIpsum.paragraph,
- message: FFaker::HipsterIpsum.sentence,
+ 'John Doe',
+ 'This is the file content',
+ message: 'This is a good commit message',
branch_name: unprotected_branch)
rugged = project.repository.rugged
author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 8eaf7aac264..36f0e6507c8 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -1,21 +1,8 @@
require 'spec_helper'
describe Gitlab::Git, lib: true do
- let(:committer_email) { FFaker::Internet.email }
-
- # I have to remove periods from the end of the name
- # This happened when the user's name had a suffix (i.e. "Sr.")
- # This seems to be what git does under the hood. For example, this commit:
- #
- # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
- #
- # results in this:
- #
- # $ git show --pretty
- # ...
- # Author: Foo Sr <foo@example.com>
- # ...
- let(:committer_name) { FFaker::Name.name.chomp("\.") }
+ let(:committer_email) { 'user@example.org' }
+ let(:committer_name) { 'John Doe' }
describe 'committer_hash' do
it "returns a hash containing the given email and name" do
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
new file mode 100644
index 00000000000..55fcf91fb6e
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient, lib: true do
+ describe '.new_channel' do
+ context 'when passed a UNIX socket address' do
+ it 'passes the address as-is to GRPC::Core::Channel initializer' do
+ address = 'unix:/tmp/gitaly.sock'
+
+ expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
+
+ described_class.new_channel(address)
+ end
+ end
+
+ context 'when passed a TCP address' do
+ it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
+ address = 'localhost:9876'
+ prefixed_address = "tcp://#{address}"
+
+ expect(GRPC::Core::Channel).to receive(:new).with(address, any_args)
+
+ described_class.new_channel(prefixed_address)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 9b9f7e4d34e..b7c59918a76 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -64,7 +64,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ imported: true
}
expect(pull_request.attributes).to eq(expected)
@@ -90,7 +91,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ imported: true
}
expect(pull_request.attributes).to eq(expected)
@@ -117,7 +119,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ imported: true
}
expect(pull_request.attributes).to eq(expected)
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index cb7c810124e..a5b7d9ad35f 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -180,24 +180,70 @@ describe Gitlab::Workhorse, lib: true do
describe '.git_http_ok' do
let(:user) { create(:user) }
let(:repo_path) { repository.path_to_repo }
+ let(:action) { 'info_refs' }
- subject { described_class.git_http_ok(repository, user) }
+ subject { described_class.git_http_ok(repository, user, action) }
- it { expect(subject).to eq({ GL_ID: "user-#{user.id}", RepoPath: repo_path }) }
+ it { expect(subject).to include({ GL_ID: "user-#{user.id}", RepoPath: repo_path }) }
context 'when Gitaly is enabled' do
+ let(:gitaly_params) do
+ {
+ GitalySocketPath: URI(Gitlab::GitalyClient.get_address('default')).path,
+ }
+ end
+
before do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end
- it 'includes Gitaly params in the returned value' do
- gitaly_socket_path = URI(Gitlab::GitalyClient.get_address('default')).path
- expect(subject).to include({ GitalySocketPath: gitaly_socket_path })
- expect(subject[:Repository]).to include({
+ it 'includes a Repository param' do
+ repo_param = { Repository: {
path: repo_path,
storage_name: 'default',
relative_path: project.full_path + '.git',
- })
+ } }
+
+ expect(subject).to include(repo_param)
+ end
+
+ context "when git_upload_pack action is passed" do
+ let(:action) { 'git_upload_pack' }
+ let(:feature_flag) { :post_upload_pack }
+
+ context 'when action is enabled by feature flag' do
+ it 'includes Gitaly params in the returned value' do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag).and_return(true)
+
+ expect(subject).to include(gitaly_params)
+ end
+ end
+
+ context 'when action is not enabled by feature flag' do
+ it 'does not include Gitaly params in the returned value' do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag).and_return(false)
+
+ expect(subject).not_to include(gitaly_params)
+ end
+ end
+ end
+
+ context "when git_receive_pack action is passed" do
+ let(:action) { 'git_receive_pack' }
+
+ it { expect(subject).not_to include(gitaly_params) }
+ end
+
+ context "when info_refs action is passed" do
+ let(:action) { 'info_refs' }
+
+ it { expect(subject).to include(gitaly_params) }
+ end
+
+ context 'when action passed is not supported by Gitaly' do
+ let(:action) { 'download' }
+
+ it { expect { subject }.to raise_exception('Unsupported action: download') }
end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f60c5ffb32a..6a89b007f96 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -37,7 +37,7 @@ describe Notify do
context 'for issues' do
let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) }
- let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: FFaker::Lorem.sentence) }
+ let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: 'My awesome description') }
describe 'that are new' do
subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
@@ -187,7 +187,7 @@ describe Notify do
let(:project) { create(:project, :repository) }
let(:merge_author) { create(:user) }
let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
- let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: FFaker::Lorem.sentence) }
+ let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: 'My awesome description') }
describe 'that are new' do
subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 53282b999dc..e4a24fd63c2 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1055,10 +1055,13 @@ describe Ci::Pipeline, models: true do
end
before do
- reset_delivered_emails!
-
project.team << [pipeline.user, Gitlab::Access::DEVELOPER]
+ pipeline.user.global_notification_setting.
+ update(level: 'custom', failed_pipeline: true, success_pipeline: true)
+
+ reset_delivered_emails!
+
perform_enqueued_jobs do
pipeline.enqueue
pipeline.run
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 55483fc876a..4f33f3c6d69 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -13,7 +13,7 @@ describe 'CycleAnalytics#plan', feature: true do
data_fn: -> (context) do
{
issue: context.create(:issue, project: context.project),
- branch_name: context.random_git_name
+ branch_name: context.generate(:branch)
}
end,
start_time_conditions: [["issue associated with a milestone",
@@ -35,7 +35,7 @@ describe 'CycleAnalytics#plan', feature: true do
context "when a regular label (instead of a list label) is added to the issue" do
it "returns nil" do
- branch_name = random_git_name
+ branch_name = generate(:branch)
label = create(:label)
issue = create(:issue, project: project)
issue.update(label_ids: [label.id])
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index e6a826a9418..4744b9e05ea 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -23,7 +23,7 @@ describe 'CycleAnalytics#production', feature: true do
# Make other changes on master
sha = context.project.repository.create_file(
context.user,
- context.random_git_name,
+ context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 3a02ed81adb..f78d7a23105 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -28,7 +28,7 @@ describe 'CycleAnalytics#staging', feature: true do
# Make other changes on master
sha = context.project.repository.create_file(
context.user,
- context.random_git_name,
+ context.generate(:branch),
'content',
message: 'commit message',
branch_name: 'master')
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index bf7950ef1c9..e69eb0098dd 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -4,7 +4,7 @@ describe KubernetesService, models: true, caching: true do
include KubernetesHelpers
include ReactiveCachingHelpers
- let(:project) { create(:kubernetes_project) }
+ let(:project) { build_stubbed(:kubernetes_project) }
let(:service) { project.kubernetes_service }
# We use Kubeclient to interactive with the Kubernetes API. It will
@@ -32,7 +32,8 @@ describe KubernetesService, models: true, caching: true do
describe 'Validations' do
context 'when service is active' do
before { subject.active = true }
- it { is_expected.to validate_presence_of(:namespace) }
+
+ it { is_expected.not_to validate_presence_of(:namespace) }
it { is_expected.to validate_presence_of(:api_url) }
it { is_expected.to validate_presence_of(:token) }
@@ -55,7 +56,7 @@ describe KubernetesService, models: true, caching: true do
'a.b' => false,
'a*b' => false,
}.each do |namespace, validity|
- it "should validate #{namespace} as #{validity ? 'valid' : 'invalid'}" do
+ it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do
subject.namespace = namespace
expect(subject.valid?).to eq(validity)
@@ -66,24 +67,40 @@ describe KubernetesService, models: true, caching: true do
context 'when service is inactive' do
before { subject.active = false }
- it { is_expected.not_to validate_presence_of(:namespace) }
+
it { is_expected.not_to validate_presence_of(:api_url) }
it { is_expected.not_to validate_presence_of(:token) }
end
end
describe '#initialize_properties' do
- context 'with a project' do
- let(:namespace_name) { "#{project.path}-#{project.id}" }
+ context 'without a project' do
+ it 'leaves the namespace unset' do
+ expect(described_class.new.namespace).to be_nil
+ end
+ end
+ end
+
+ describe '#fields' do
+ let(:kube_namespace) do
+ subject.fields.find { |h| h[:name] == 'namespace' }
+ end
+
+ context 'as template' do
+ before { subject.template = true }
- it 'defaults to the project name with ID' do
- expect(described_class.new(project: project).namespace).to eq(namespace_name)
+ it 'sets the namespace to the default' do
+ expect(kube_namespace).not_to be_nil
+ expect(kube_namespace[:placeholder]).to eq(subject.class::TEMPLATE_PLACEHOLDER)
end
end
- context 'without a project' do
- it 'leaves the namespace unset' do
- expect(described_class.new.namespace).to be_nil
+ context 'with associated project' do
+ before { subject.project = project }
+
+ it 'sets the namespace to the default' do
+ expect(kube_namespace).not_to be_nil
+ expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
end
end
end
@@ -138,38 +155,40 @@ describe KubernetesService, models: true, caching: true do
before do
subject.api_url = 'https://kube.domain.com'
subject.token = 'token'
- subject.namespace = 'my-project'
subject.ca_pem = 'CA PEM DATA'
+ subject.project = project
end
- it 'sets KUBE_URL' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }
- )
- end
+ context 'namespace is provided' do
+ before { subject.namespace = 'my-project' }
- it 'sets KUBE_TOKEN' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_TOKEN', value: 'token', public: false }
- )
+ it 'sets the variables' do
+ expect(subject.predefined_variables).to include(
+ { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
+ { key: 'KUBE_TOKEN', value: 'token', public: false },
+ { key: 'KUBE_NAMESPACE', value: 'my-project', public: true },
+ { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
+ { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true },
+ )
+ end
end
- it 'sets KUBE_NAMESPACE' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_NAMESPACE', value: 'my-project', public: true }
- )
- end
+ context 'no namespace provided' do
+ it 'sets the variables' do
+ expect(subject.predefined_variables).to include(
+ { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
+ { key: 'KUBE_TOKEN', value: 'token', public: false },
+ { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
+ { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true },
+ )
+ end
- it 'sets KUBE_CA_PEM' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }
- )
- end
+ it 'sets the KUBE_NAMESPACE' do
+ kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
- it 'sets KUBE_CA_PEM_FILE' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
- )
+ expect(kube_namespace).not_to be_nil
+ expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index df742ee8084..d805e65b3c6 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -24,21 +24,8 @@ describe Repository, models: true do
repository.commit(merge_commit_id)
end
- let(:author_email) { FFaker::Internet.email }
-
- # I have to remove periods from the end of the name
- # This happened when the user's name had a suffix (i.e. "Sr.")
- # This seems to be what git does under the hood. For example, this commit:
- #
- # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
- #
- # results in this:
- #
- # $ git show --pretty
- # ...
- # Author: Foo Sr <foo@example.com>
- # ...
- let(:author_name) { FFaker::Name.name.chomp("\.") }
+ let(:author_email) { 'user@example.org' }
+ let(:author_name) { 'John Doe' }
describe '#branch_names_contains' do
subject { repository.branch_names_contains(sample_commit.id) }
@@ -1293,14 +1280,6 @@ describe Repository, models: true do
end
end
- describe '#before_import' do
- it 'flushes the repository caches' do
- expect(repository).to receive(:expire_content_cache)
-
- repository.before_import
- end
- end
-
describe '#after_import' do
it 'flushes and builds the cache' do
expect(repository).to receive(:expire_content_cache)
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index a7fad7f0bdb..8012530f139 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -11,21 +11,8 @@ describe API::Files, api: true do
ref: 'master'
}
end
- let(:author_email) { FFaker::Internet.email }
-
- # I have to remove periods from the end of the name
- # This happened when the user's name had a suffix (i.e. "Sr.")
- # This seems to be what git does under the hood. For example, this commit:
- #
- # $ git commit --author='Foo Sr. <foo@example.com>' -m 'Where's my trailing period?'
- #
- # results in this:
- #
- # $ git show --pretty
- # ...
- # Author: Foo Sr <foo@example.com>
- # ...
- let(:author_name) { FFaker::Name.name.chomp("\.") }
+ let(:author_email) { 'user@example.org' }
+ let(:author_name) { 'John Doe' }
before { project.team << [user, :developer] }
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 91d6fb83c0b..4729adba11c 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -19,7 +19,7 @@ describe API::Issues, api: true do
project: project,
state: :closed,
milestone: milestone,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 3.hours.ago
end
let!(:confidential_issue) do
@@ -28,7 +28,7 @@ describe API::Issues, api: true do
project: project,
author: author,
assignee: assignee,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 2.hours.ago
end
let!(:issue) do
@@ -37,7 +37,7 @@ describe API::Issues, api: true do
assignee: user,
project: project,
milestone: milestone,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 1.hour.ago
end
let!(:label) do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 2d37d026a39..84dca51801f 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::Members, api: true do
include ApiHelpers
- let(:master) { create(:user) }
+ let(:master) { create(:user, username: 'master_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
let(:stranger) { create(:user) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a3de4702ad0..2e291eb3cea 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -341,7 +341,6 @@ describe API::Projects, :api do
it "assigns attributes to project" do
project = attributes_for(:project, {
path: 'camelCasePath',
- description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
@@ -475,7 +474,6 @@ describe API::Projects, :api do
it 'assigns attributes to project' do
project = attributes_for(:project, {
- description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 3b61139a2cd..349fd6b3415 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -26,8 +26,8 @@ describe API::V3::Files, api: true do
ref: 'master'
}
end
- let(:author_email) { FFaker::Internet.email }
- let(:author_name) { FFaker::Name.name.chomp("\.") }
+ let(:author_email) { 'user@example.org' }
+ let(:author_name) { 'John Doe' }
before { project.team << [user, :developer] }
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index 383871d5c38..b1b398a897e 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -19,7 +19,7 @@ describe API::V3::Issues, api: true do
project: project,
state: :closed,
milestone: milestone,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 3.hours.ago
end
let!(:confidential_issue) do
@@ -28,7 +28,7 @@ describe API::V3::Issues, api: true do
project: project,
author: author,
assignee: assignee,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 2.hours.ago
end
let!(:issue) do
@@ -37,7 +37,7 @@ describe API::V3::Issues, api: true do
assignee: user,
project: project,
milestone: milestone,
- created_at: generate(:issue_created_at),
+ created_at: generate(:past_time),
updated_at: 1.hour.ago
end
let!(:label) do
diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb
index 13814ed10c3..af1c5cff67f 100644
--- a/spec/requests/api/v3/members_spec.rb
+++ b/spec/requests/api/v3/members_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::V3::Members, api: true do
include ApiHelpers
- let(:master) { create(:user) }
+ let(:master) { create(:user, username: 'master_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
let(:stranger) { create(:user) }
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index b1aa793ec00..40531fe7545 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -356,7 +356,6 @@ describe API::V3::Projects, api: true do
it "assigns attributes to project" do
project = attributes_for(:project, {
path: 'camelCasePath',
- description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
@@ -501,7 +500,6 @@ describe API::V3::Projects, api: true do
it 'assigns attributes to project' do
project = attributes_for(:project, {
- description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false,
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index d93616c4f50..bb98fb37a90 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -418,65 +418,6 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
- context 'when there are builds that are not created yet' do
- let(:pipeline) do
- create(:ci_pipeline, config: config)
- end
-
- let(:config) do
- { rspec: { stage: 'test', script: 'rspec' },
- deploy: { stage: 'deploy', script: 'rsync' } }
- end
-
- before do
- create_build('linux', stage: 'build', stage_idx: 0)
- create_build('mac', stage: 'build', stage_idx: 0)
- end
-
- it 'processes the pipeline' do
- # Currently we have five builds with state created
- #
- expect(builds.count).to eq(0)
- expect(all_builds.count).to eq(2)
-
- # Process builds service will enqueue builds from the first stage.
- #
- process_pipeline
-
- expect(builds.count).to eq(2)
- expect(all_builds.count).to eq(2)
-
- # When builds succeed we will enqueue remaining builds.
- #
- # We will have 2 succeeded, 1 pending (from stage test), total 4 (two
- # additional build from `.gitlab-ci.yml`).
- #
- succeed_pending
- process_pipeline
-
- expect(builds.success.count).to eq(2)
- expect(builds.pending.count).to eq(1)
- expect(all_builds.count).to eq(4)
-
- # When pending merge_when_pipeline_succeeds in stage test, we enqueue deploy stage.
- #
- succeed_pending
- process_pipeline
-
- expect(builds.pending.count).to eq(1)
- expect(builds.success.count).to eq(3)
- expect(all_builds.count).to eq(4)
-
- # When the last one succeeds we have 4 successful builds.
- #
- succeed_pending
- process_pipeline
-
- expect(builds.success.count).to eq(4)
- expect(all_builds.count).to eq(4)
- end
- end
-
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 5c841843b40..e3146a56495 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -113,7 +113,7 @@ describe NotificationService, services: true do
project.add_master(issue.assignee)
project.add_master(note.author)
create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
- update_custom_notification(:new_note, @u_guest_custom, project)
+ update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global)
end
@@ -379,7 +379,7 @@ describe NotificationService, services: true do
build_team(note.project)
reset_delivered_emails!
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
- update_custom_notification(:new_note, @u_guest_custom, project)
+ update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global)
end
@@ -457,7 +457,7 @@ describe NotificationService, services: true do
add_users_with_subscription(issue.project, issue)
reset_delivered_emails!
- update_custom_notification(:new_issue, @u_guest_custom, project)
+ update_custom_notification(:new_issue, @u_guest_custom, resource: project)
update_custom_notification(:new_issue, @u_custom_global)
end
@@ -567,7 +567,7 @@ describe NotificationService, services: true do
describe '#reassigned_issue' do
before do
- update_custom_notification(:reassign_issue, @u_guest_custom, project)
+ update_custom_notification(:reassign_issue, @u_guest_custom, resource: project)
update_custom_notification(:reassign_issue, @u_custom_global)
end
@@ -760,7 +760,7 @@ describe NotificationService, services: true do
describe '#close_issue' do
before do
- update_custom_notification(:close_issue, @u_guest_custom, project)
+ update_custom_notification(:close_issue, @u_guest_custom, resource: project)
update_custom_notification(:close_issue, @u_custom_global)
end
@@ -791,7 +791,7 @@ describe NotificationService, services: true do
describe '#reopen_issue' do
before do
- update_custom_notification(:reopen_issue, @u_guest_custom, project)
+ update_custom_notification(:reopen_issue, @u_guest_custom, resource: project)
update_custom_notification(:reopen_issue, @u_custom_global)
end
@@ -856,14 +856,14 @@ describe NotificationService, services: true do
before do
build_team(merge_request.target_project)
add_users_with_subscription(merge_request.target_project, merge_request)
- update_custom_notification(:new_merge_request, @u_guest_custom, project)
+ update_custom_notification(:new_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:new_merge_request, @u_custom_global)
reset_delivered_emails!
end
describe '#new_merge_request' do
before do
- update_custom_notification(:new_merge_request, @u_guest_custom, project)
+ update_custom_notification(:new_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:new_merge_request, @u_custom_global)
end
@@ -952,7 +952,7 @@ describe NotificationService, services: true do
describe '#reassigned_merge_request' do
before do
- update_custom_notification(:reassign_merge_request, @u_guest_custom, project)
+ update_custom_notification(:reassign_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:reassign_merge_request, @u_custom_global)
end
@@ -1026,7 +1026,7 @@ describe NotificationService, services: true do
describe '#closed_merge_request' do
before do
- update_custom_notification(:close_merge_request, @u_guest_custom, project)
+ update_custom_notification(:close_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:close_merge_request, @u_custom_global)
end
@@ -1056,7 +1056,7 @@ describe NotificationService, services: true do
describe '#merged_merge_request' do
before do
- update_custom_notification(:merge_merge_request, @u_guest_custom, project)
+ update_custom_notification(:merge_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:merge_merge_request, @u_custom_global)
end
@@ -1108,7 +1108,7 @@ describe NotificationService, services: true do
describe '#reopen_merge_request' do
before do
- update_custom_notification(:reopen_merge_request, @u_guest_custom, project)
+ update_custom_notification(:reopen_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:reopen_merge_request, @u_custom_global)
end
@@ -1281,40 +1281,172 @@ describe NotificationService, services: true do
describe 'Pipelines' do
describe '#pipeline_finished' do
let(:project) { create(:project, :public, :repository) }
- let(:current_user) { create(:user) }
let(:u_member) { create(:user) }
- let(:u_other) { create(:user) }
+ let(:u_watcher) { create_user_with_notification(:watch, 'watcher') }
+
+ let(:u_custom_notification_unset) do
+ create_user_with_notification(:custom, 'custom_unset')
+ end
+
+ let(:u_custom_notification_enabled) do
+ user = create_user_with_notification(:custom, 'custom_enabled')
+ update_custom_notification(:success_pipeline, user, resource: project)
+ update_custom_notification(:failed_pipeline, user, resource: project)
+ user
+ end
+
+ let(:u_custom_notification_disabled) do
+ user = create_user_with_notification(:custom, 'custom_disabled')
+ update_custom_notification(:success_pipeline, user, resource: project, value: false)
+ update_custom_notification(:failed_pipeline, user, resource: project, value: false)
+ user
+ end
let(:commit) { project.commit }
- let(:pipeline) do
- create(:ci_pipeline, :success,
+
+ def create_pipeline(user, status)
+ create(:ci_pipeline, status,
project: project,
- user: current_user,
+ user: user,
ref: 'refs/heads/master',
sha: commit.id,
before_sha: '00000000')
end
before do
- project.add_master(current_user)
project.add_master(u_member)
+ project.add_master(u_watcher)
+ project.add_master(u_custom_notification_unset)
+ project.add_master(u_custom_notification_enabled)
+ project.add_master(u_custom_notification_disabled)
+
reset_delivered_emails!
end
- context 'without custom recipients' do
- it 'notifies the pipeline user' do
- notification.pipeline_finished(pipeline)
+ context 'with a successful pipeline' do
+ context 'when the creator has default settings' do
+ before do
+ pipeline = create_pipeline(u_member, :success)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'notifies nobody' do
+ should_not_email_anyone
+ end
+ end
+
+ context 'when the creator has watch set' do
+ before do
+ pipeline = create_pipeline(u_watcher, :success)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'notifies nobody' do
+ should_not_email_anyone
+ end
+ end
+
+ context 'when the creator has custom notifications, but without any set' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_unset, :success)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'notifies nobody' do
+ should_not_email_anyone
+ end
+ end
+
+ context 'when the creator has custom notifications disabled' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_disabled, :success)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'notifies nobody' do
+ should_not_email_anyone
+ end
+ end
+
+ context 'when the creator has custom notifications enabled' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_enabled, :success)
+ notification.pipeline_finished(pipeline)
+ end
- should_only_email(current_user, kind: :bcc)
+ it 'emails only the creator' do
+ should_only_email(u_custom_notification_enabled, kind: :bcc)
+ end
end
end
- context 'with custom recipients' do
- it 'notifies the custom recipients' do
- users = [u_member, u_other]
- notification.pipeline_finished(pipeline, users.map(&:notification_email))
+ context 'with a failed pipeline' do
+ context 'when the creator has no custom notification set' do
+ before do
+ pipeline = create_pipeline(u_member, :failed)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'emails only the creator' do
+ should_only_email(u_member, kind: :bcc)
+ end
+ end
+
+ context 'when the creator has watch set' do
+ before do
+ pipeline = create_pipeline(u_watcher, :failed)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'emails only the creator' do
+ should_only_email(u_watcher, kind: :bcc)
+ end
+ end
+
+ context 'when the creator has custom notifications, but without any set' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_unset, :failed)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'emails only the creator' do
+ should_only_email(u_custom_notification_unset, kind: :bcc)
+ end
+ end
+
+ context 'when the creator has custom notifications disabled' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_disabled, :failed)
+ notification.pipeline_finished(pipeline)
+ end
- should_only_email(*users, kind: :bcc)
+ it 'notifies nobody' do
+ should_not_email_anyone
+ end
+ end
+
+ context 'when the creator has custom notifications set' do
+ before do
+ pipeline = create_pipeline(u_custom_notification_enabled, :failed)
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'emails only the creator' do
+ should_only_email(u_custom_notification_enabled, kind: :bcc)
+ end
+ end
+
+ context 'when the creator has no read_build access' do
+ before do
+ pipeline = create_pipeline(u_member, :failed)
+ project.update(public_builds: false)
+ project.team.truncate
+ notification.pipeline_finished(pipeline)
+ end
+
+ it 'does not send emails' do
+ should_not_email_anyone
+ end
end
end
end
@@ -1385,9 +1517,9 @@ describe NotificationService, services: true do
# Create custom notifications
# When resource is nil it means global notification
- def update_custom_notification(event, user, resource = nil)
+ def update_custom_notification(event, user, resource: nil, value: true)
setting = user.notification_settings_for(resource)
- setting.events[event] = true
+ setting.events[event] = value
setting.save
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index e5917bb0b7a..09cfa36b3b9 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -26,30 +26,59 @@ describe Projects::ImportService, services: true do
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'The repository could not be created.'
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The repository could not be created."
end
end
context 'with known url' do
before do
project.import_url = 'https://github.com/vim/vim.git'
+ project.import_type = 'github'
end
- it 'succeeds if repository import is successfully' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true)
+ context 'with a Github repository' do
+ it 'succeeds if repository import is successfully' do
+ expect_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
- result = subject.execute
+ result = subject.execute
- expect(result[:status]).to eq :success
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository import fails' do
+ expect_any_instance_of(Repository).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
+ end
end
- it 'fails if repository import fails' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+ context 'with a non Github repository' do
+ before do
+ project.import_url = 'https://bitbucket.org/vim/vim.git'
+ project.import_type = 'bitbucket'
+ end
- result = subject.execute
+ it 'succeeds if repository import is successfully' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
+ expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true)
- expect(result[:status]).to eq :error
- expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository import fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
+ end
end
end
@@ -64,8 +93,8 @@ describe Projects::ImportService, services: true do
end
it 'succeeds if importer succeeds' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+ allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
+ allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
result = subject.execute
@@ -73,48 +102,42 @@ describe Projects::ImportService, services: true do
end
it 'flushes various caches' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).
- with(project.repository_storage_path, project.path_with_namespace, project.import_url).
+ allow_any_instance_of(Repository).to receive(:fetch_remote).
and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).
+ allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).
and_return(true)
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
- and_call_original
-
- expect_any_instance_of(Repository).to receive(:expire_exists_cache).
- and_call_original
+ expect_any_instance_of(Repository).to receive(:expire_content_cache)
subject.execute
end
it 'fails if importer fails' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+ allow_any_instance_of(Repository).to receive(:fetch_remote).and_return(true)
+ allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'The remote data could not be imported.'
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - The remote data could not be imported."
end
it 'fails if importer raise an error' do
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true)
- expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+ allow_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_return(true)
+ allow_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'Github: failed to connect API'
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Github: failed to connect API"
end
- it 'expires existence cache after error' do
+ it 'expires content cache after error' do
allow_any_instance_of(Project).to receive(:repository_exists?).and_return(false, true)
- expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).and_call_original
- expect_any_instance_of(Repository).to receive(:expire_exists_cache).and_call_original
+ expect_any_instance_of(Gitlab::Shell).to receive(:fetch_remote).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+ expect_any_instance_of(Repository).to receive(:expire_content_cache)
subject.execute
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 90cde705b85..5ec1ed8237b 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -221,26 +221,23 @@ describe SystemNoteService, services: true do
describe '.change_status' do
subject { described_class.change_status(noteable, project, author, status, source) }
- let(:status) { 'new_status' }
- let(:source) { nil }
+ context 'with status reopened' do
+ let(:status) { 'reopened' }
+ let(:source) { nil }
- it_behaves_like 'a system note' do
- let(:action) { 'status' }
+ it_behaves_like 'a system note' do
+ let(:action) { 'opened' }
+ end
end
context 'with a source' do
+ let(:status) { 'opened' }
let(:source) { double('commit', gfm_reference: 'commit 123456') }
it 'sets the note text' do
expect(subject.note).to eq "#{status} via commit 123456"
end
end
-
- context 'without a source' do
- it 'sets the note text' do
- expect(subject.note).to eq status
- end
- end
end
describe '.merge_when_pipeline_succeeds' do
@@ -298,9 +295,23 @@ describe SystemNoteService, services: true do
describe '.change_issue_confidentiality' do
subject { described_class.change_issue_confidentiality(noteable, project, author) }
- context 'when noteable responds to `confidential`' do
+ context 'issue has been made confidential' do
+ before do
+ noteable.update_attribute(:confidential, true)
+ end
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'confidential' }
+ end
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'made the issue confidential'
+ end
+ end
+
+ context 'issue has been made visible' do
it_behaves_like 'a system note' do
- let(:action) { 'confidentiality' }
+ let(:action) { 'visible' }
end
it 'sets the note text' do
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index c864a705ca4..8ad042f5e3b 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -1,5 +1,5 @@
module CycleAnalyticsHelpers
- def create_commit_referencing_issue(issue, branch_name: random_git_name)
+ def create_commit_referencing_issue(issue, branch_name: generate(:branch))
project.repository.add_branch(user, branch_name, 'master')
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end
@@ -7,9 +7,7 @@ module CycleAnalyticsHelpers
def create_commit(message, project, user, branch_name, count: 1)
oldrev = project.repository.commit(branch_name).sha
commit_shas = Array.new(count) do |index|
- filename = random_git_name
-
- commit_sha = project.repository.create_file(user, filename, "content", message: message, branch_name: branch_name)
+ commit_sha = project.repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name)
project.repository.commit(commit_sha)
commit_sha
@@ -24,13 +22,13 @@ module CycleAnalyticsHelpers
def create_merge_request_closing_issue(issue, message: nil, source_branch: nil)
if !source_branch || project.repository.commit(source_branch).blank?
- source_branch = random_git_name
+ source_branch = generate(:branch)
project.repository.add_branch(user, source_branch, 'master')
end
sha = project.repository.create_file(
user,
- random_git_name,
+ generate(:branch),
'content',
message: 'commit message',
branch_name: source_branch)
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index a8e454eb09e..b871b7ffc90 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -63,9 +63,9 @@ module FilterSpecHelper
#
# Returns a String
def invalidate_reference(reference)
- if reference =~ /\A(.+)?.\d+\z/
+ if reference =~ /\A(.+)?[^\d]\d+\z/
# Integer-based reference with optional project prefix
- reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
+ reference.gsub(/\d+\z/) { |i| i.to_i + 10_000 }
elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix
reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
diff --git a/spec/support/git_helpers.rb b/spec/support/git_helpers.rb
deleted file mode 100644
index 93422390ef7..00000000000
--- a/spec/support/git_helpers.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module GitHelpers
- def random_git_name
- "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
- end
-end
-
-RSpec.configure do |config|
- config.include GitHelpers
-end
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index 4c0f556e736..3406e4c3161 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -2,12 +2,12 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
before do
@issuable_ids = []
- 2.times do
+ 2.times do |n|
issuable =
if issuable_type == :issue
create(issuable_type, project: project)
else
- create(issuable_type, title: FFaker::Lorem.sentence, source_project: project, source_branch: FFaker::Name.name)
+ create(issuable_type, source_project: project, source_branch: "#{n}-feature")
end
@issuable_ids << issuable.id
@@ -33,4 +33,19 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
expect(meta_data[id].upvotes).to eq(id + 2)
end
end
+
+ describe "when given empty collection" do
+ let(:project2) { create(:empty_project, :public) }
+
+ it "doesn't execute any queries with false conditions" do
+ get_action =
+ if action
+ proc { get action }
+ else
+ proc { get :index, namespace_id: project2.namespace, project_id: project2 }
+ end
+
+ expect(&get_action).not_to make_queries_matching(/WHERE (?:1=0|0=1)/)
+ end
+ end
end
diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb
new file mode 100644
index 00000000000..ac8c4ab91d9
--- /dev/null
+++ b/spec/support/matchers/query_matcher.rb
@@ -0,0 +1,33 @@
+RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
+ supports_block_expectations
+
+ match do |block|
+ @counter = query_count(matcher, &block)
+ if expected_count
+ @counter.count == expected_count
+ else
+ @counter.count > 0
+ end
+ end
+
+ failure_message_when_negated do |_|
+ if expected_count
+ "expected #{matcher} not to match #{expected_count} queries, got #{@counter.count} matches:\n\n#{@counter.inspect}"
+ else
+ "expected #{matcher} not to match any query, got #{@counter.count} matches:\n\n#{@counter.inspect}"
+ end
+ end
+
+ failure_message do |_|
+ if expected_count
+ "expected #{matcher} to match #{expected_count} queries, got #{@counter.count} matches:\n\n#{@counter.inspect}"
+ else
+ "expected #{matcher} to match at least one query, got #{@counter.count} matches:\n\n#{@counter.inspect}"
+ end
+ end
+
+ def query_count(regex, &block)
+ @recorder = ActiveRecord::QueryRecorder.new(&block).log
+ @recorder.select{ |q| q.match(regex) }
+ end
+end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index ee492daee30..9e9cdf3e48b 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -7,7 +7,7 @@ shared_examples 'new issuable record that supports slash commands' do
let(:assignee) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
let!(:labels) { create_list(:label, 3, project: project) }
- let(:base_params) { { title: FFaker::Lorem.sentence(3) } }
+ let(:base_params) { { title: 'My issuable title' } }
let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) }
let(:issuable) { described_class.new(project, user, params).execute }
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index 5a7ce2e08c4..139032d77bd 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -3,131 +3,19 @@ require 'spec_helper'
describe PipelineNotificationWorker do
include EmailHelpers
- let(:pipeline) do
- create(:ci_pipeline,
- project: project,
- sha: project.commit('master').sha,
- user: pusher,
- status: status)
- end
-
- let(:project) { create(:project, :repository, public_builds: false) }
- let(:user) { create(:user) }
- let(:pusher) { user }
- let(:watcher) { pusher }
+ let(:pipeline) { create(:ci_pipeline) }
describe '#execute' do
- before do
- reset_delivered_emails!
- pipeline.project.team << [pusher, Gitlab::Access::DEVELOPER]
- end
-
- context 'when watcher has developer access' do
- before do
- pipeline.project.team << [watcher, Gitlab::Access::DEVELOPER]
- end
-
- shared_examples 'sending emails' do
- it 'sends emails' do
- perform_enqueued_jobs do
- subject.perform(pipeline.id)
- end
-
- emails = ActionMailer::Base.deliveries
- actual = emails.flat_map(&:bcc).sort
- expected_receivers = receivers.map(&:email).uniq.sort
-
- expect(actual).to eq(expected_receivers)
- expect(emails.size).to eq(1)
- expect(emails.last.subject).to include(email_subject)
- end
- end
-
- context 'with success pipeline' do
- let(:status) { 'success' }
- let(:email_subject) { "Pipeline ##{pipeline.id} has succeeded" }
- let(:receivers) { [pusher, watcher] }
-
- it_behaves_like 'sending emails'
-
- context 'with pipeline from someone else' do
- let(:pusher) { create(:user) }
- let(:watcher) { user }
-
- context 'with success pipeline notification on' do
- before do
- watcher.global_notification_setting.
- update(level: 'custom', success_pipeline: true)
- end
-
- it_behaves_like 'sending emails'
- end
-
- context 'with success pipeline notification off' do
- let(:receivers) { [pusher] }
+ it 'calls NotificationService#pipeline_finished when the pipeline exists' do
+ expect(NotificationService).to receive_message_chain(:new, :pipeline_finished)
- before do
- watcher.global_notification_setting.
- update(level: 'custom', success_pipeline: false)
- end
-
- it_behaves_like 'sending emails'
- end
- end
-
- context 'with failed pipeline' do
- let(:status) { 'failed' }
- let(:email_subject) { "Pipeline ##{pipeline.id} has failed" }
-
- it_behaves_like 'sending emails'
-
- context 'with pipeline from someone else' do
- let(:pusher) { create(:user) }
- let(:watcher) { user }
-
- context 'with failed pipeline notification on' do
- before do
- watcher.global_notification_setting.
- update(level: 'custom', failed_pipeline: true)
- end
-
- it_behaves_like 'sending emails'
- end
-
- context 'with failed pipeline notification off' do
- let(:receivers) { [pusher] }
-
- before do
- watcher.global_notification_setting.
- update(level: 'custom', failed_pipeline: false)
- end
-
- it_behaves_like 'sending emails'
- end
- end
- end
- end
+ subject.perform(pipeline.id)
end
- context 'when watcher has no read_build access' do
- let(:status) { 'failed' }
- let(:email_subject) { "Pipeline ##{pipeline.id} has failed" }
- let(:watcher) { create(:user) }
-
- before do
- pipeline.project.team << [watcher, Gitlab::Access::GUEST]
-
- watcher.global_notification_setting.
- update(level: 'custom', failed_pipeline: true)
-
- perform_enqueued_jobs do
- subject.perform(pipeline.id)
- end
- end
+ it 'does nothing when the pipeline does not exist' do
+ expect(NotificationService).not_to receive(:new)
- it 'does not send emails' do
- should_only_email(pusher, kind: :bcc)
- end
+ subject.perform(Ci::Pipeline.maximum(:id).to_i.succ)
end
end
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 1c383d0514d..9afe2e610b9 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -99,6 +99,13 @@ describe ProcessCommitWorker do
expect(metric.first_mentioned_in_commit_at).to eq(commit.committed_date)
end
+
+ it "doesn't execute any queries with false conditions" do
+ allow(commit).to receive(:safe_message).
+ and_return("Lorem Ipsum")
+
+ expect { worker.update_issue_metrics(commit, user) }.not_to make_queries_matching(/WHERE (?:1=0|0=1)/)
+ end
end
describe '#build_commit' do