summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile.rails5.lock6
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue4
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue4
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/new/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_new.js152
-rw-r--r--app/assets/javascripts/pages/projects/shared/save_project_loader.js12
-rw-r--r--app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue18
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediator.js6
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js13
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js17
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue20
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/help_state.vue (renamed from app/assets/javascripts/sidebar/components/time_tracking/help_state.js)49
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue8
-rw-r--r--app/assets/javascripts/visibility_select.js21
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue10
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/login.scss20
-rw-r--r--app/assets/stylesheets/pages/note_form.scss13
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss5
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb4
-rw-r--r--app/controllers/concerns/renders_notes.rb2
-rw-r--r--app/controllers/groups_controller.rb4
-rw-r--r--app/controllers/projects/commit_controller.rb1
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb11
-rw-r--r--app/helpers/ci_status_helper.rb4
-rw-r--r--app/helpers/markup_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb66
-rw-r--r--app/models/broadcast_message.rb6
-rw-r--r--app/models/event.rb3
-rw-r--r--app/services/events/render_service.rb12
-rw-r--r--app/services/notes/render_service.rb13
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/devise/shared/_tab_single.html.haml2
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml2
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml2
-rw-r--r--app/views/projects/_visibility_select.html.haml9
-rw-r--r--app/views/projects/commits/_commit.html.haml3
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml2
-rw-r--r--app/views/shared/notes/_form.html.haml35
-rwxr-xr-xbin/spinach5
-rw-r--r--changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml5
-rw-r--r--changelogs/unreleased/41748-vertical-misalignment-login-box.yml5
-rw-r--r--changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml5
-rw-r--r--changelogs/unreleased/44697-prevue.yml5
-rw-r--r--changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml5
-rw-r--r--changelogs/unreleased/45159-fix-illustration.yml5
-rw-r--r--changelogs/unreleased/45287-align-icons.yml5
-rw-r--r--changelogs/unreleased/ab-37125-assigned-issues-query.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml5
-rw-r--r--changelogs/unreleased/fix-wiki-find-file-gitaly.yml5
-rw-r--r--changelogs/unreleased/fl-pipelines-details-axios.yml5
-rw-r--r--changelogs/unreleased/move-estimate-only-pane-vue-component.yml5
-rw-r--r--changelogs/unreleased/move-help-state-vue-component.yml5
-rw-r--r--changelogs/unreleased/rendering-markdown-multiple-projects.yml5
-rw-r--r--changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml5
-rw-r--r--changelogs/unreleased/winh-dropdown-entry-unlocking.yml5
-rw-r--r--changelogs/unreleased/zj-branch-containing-sha-opt-out.yml5
-rw-r--r--changelogs/unreleased/zj-tag-containing-sha-opt-out.yml5
-rw-r--r--config/karma.config.js19
-rw-r--r--db/fixtures/development/01_admin.rb20
-rw-r--r--db/fixtures/development/19_environments.rb6
-rw-r--r--doc/README.md6
-rw-r--r--doc/ci/yaml/README.md79
-rw-r--r--doc/development/i18n/externalization.md17
-rw-r--r--doc/development/testing_guide/frontend_testing.md28
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/topics/autodevops/index.md37
-rw-r--r--doc/update/10.6-to-10.7.md361
-rw-r--r--doc/user/discussions/index.md5
-rw-r--r--doc/user/project/integrations/prometheus_library/cloudwatch.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md3
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md6
-rw-r--r--features/project/commits/branches.feature42
-rw-r--r--features/project/issues/milestones.feature43
-rw-r--r--features/steps/project/commits/branches.rb57
-rw-r--r--features/steps/project/issues/milestones.rb58
-rw-r--r--features/steps/shared/markdown.rb4
-rw-r--r--features/steps/shared/paths.rb16
-rw-r--r--lib/banzai/commit_renderer.rb2
-rw-r--r--lib/banzai/filter/issuable_state_filter.rb3
-rw-r--r--lib/banzai/filter/redactor_filter.rb6
-rw-r--r--lib/banzai/issuable_extractor.rb14
-rw-r--r--lib/banzai/object_renderer.rb32
-rw-r--r--lib/banzai/redactor.rb20
-rw-r--r--lib/banzai/reference_extractor.rb4
-rw-r--r--lib/banzai/reference_parser/base_parser.rb16
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb39
-rw-r--r--lib/banzai/reference_parser/user_parser.rb3
-rw-r--r--lib/banzai/render_context.rb32
-rw-r--r--lib/gitlab/ci/status/build/common.rb8
-rw-r--r--lib/gitlab/ci/status/build/erased.rb2
-rw-r--r--lib/gitlab/ci/variables/collection/item.rb7
-rw-r--r--lib/gitlab/git.rb4
-rw-r--r--lib/gitlab/git/attributes_parser.rb12
-rw-r--r--lib/gitlab/git/info_attributes.rb49
-rw-r--r--lib/gitlab/git/raw_diff_change.rb60
-rw-r--r--lib/gitlab/git/repository.rb77
-rwxr-xr-xlib/gitlab/git/support/format-git-cat-file-input21
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb10
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb9
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb2
-rw-r--r--lib/gitlab/utils.rb4
-rw-r--r--lib/rspec_flaky/config.rb4
-rw-r--r--lib/rspec_flaky/flaky_examples_collection.rb10
-rw-r--r--lib/rspec_flaky/listener.rb39
-rw-r--r--lib/rspec_flaky/report.rb54
-rw-r--r--package.json5
-rwxr-xr-xscripts/prune-old-flaky-specs24
-rw-r--r--spec/features/dashboard/projects_spec.rb4
-rw-r--r--spec/features/milestones/show_spec.rb26
-rw-r--r--spec/features/milestones/user_creates_milestone_spec.rb29
-rw-r--r--spec/features/milestones/user_deletes_milestone_spec.rb25
-rw-r--r--spec/features/milestones/user_views_milestone_spec.rb31
-rw-r--r--spec/features/milestones/user_views_milestones_spec.rb35
-rw-r--r--spec/features/projects/branches/user_creates_branch_spec.rb46
-rw-r--r--spec/features/projects/branches/user_deletes_branch_spec.rb23
-rw-r--r--spec/features/projects/branches/user_views_branches_spec.rb34
-rw-r--r--spec/features/projects/files/user_reads_pipeline_status_spec.rb46
-rw-r--r--spec/features/projects/jobs_spec.rb11
-rw-r--r--spec/features/users/login_spec.rb2
-rw-r--r--spec/fixtures/big-image.pngbin0 -> 324444 bytes
-rw-r--r--spec/helpers/projects_helper_spec.rb68
-rw-r--r--spec/javascripts/fixtures/linked_tabs.html.haml2
-rw-r--r--spec/javascripts/fixtures/signin_tabs.html.haml8
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js39
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js36
-rw-r--r--spec/javascripts/pipelines/stage_spec.js85
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js8
-rw-r--r--spec/javascripts/test_bundle.js33
-rw-r--r--spec/javascripts/visibility_select_spec.js98
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js50
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js33
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb5
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb9
-rw-r--r--spec/lib/banzai/redactor_spec.rb4
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb18
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/render_context_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb35
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb10
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb12
-rw-r--r--spec/lib/gitlab/git/info_attributes_spec.rb43
-rw-r--r--spec/lib/gitlab/git/raw_diff_change_spec.rb66
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb38
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb8
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb11
-rw-r--r--spec/lib/gitlab/utils_spec.rb11
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb30
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb14
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb114
-rw-r--r--spec/lib/rspec_flaky/report_spec.rb125
-rw-r--r--spec/models/broadcast_message_spec.rb6
-rw-r--r--spec/models/project_wiki_spec.rb12
-rw-r--r--spec/services/events/render_service_spec.rb6
-rw-r--r--spec/services/notes/render_service_spec.rb25
-rw-r--r--spec/support/bare_repo_operations.rb10
-rw-r--r--spec/support/filter_spec_helper.rb5
-rw-r--r--spec/support/helpers/features/branches_helpers.rb33
-rw-r--r--spec/support/reference_parser_helpers.rb8
-rw-r--r--vendor/gitignore/Android.gitignore2
-rw-r--r--vendor/gitignore/Elixir.gitignore1
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore15
-rw-r--r--vendor/gitignore/Global/Windows.gitignore1
-rw-r--r--vendor/gitignore/Godot.gitignore8
-rw-r--r--vendor/gitignore/Joomla.gitignore1
-rw-r--r--vendor/gitignore/KiCad.gitignore5
-rw-r--r--vendor/gitignore/Leiningen.gitignore1
-rw-r--r--vendor/gitignore/Node.gitignore2
-rw-r--r--vendor/gitignore/Python.gitignore3
-rw-r--r--vendor/gitignore/Rails.gitignore1
-rw-r--r--vendor/gitignore/Rust.gitignore2
-rw-r--r--vendor/gitignore/TeX.gitignore2
-rw-r--r--vendor/gitignore/Unity.gitignore2
-rw-r--r--vendor/gitignore/VisualStudio.gitignore8
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml53
-rw-r--r--vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml2
-rw-r--r--vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml17
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml2
-rw-r--r--vendor/gitlab-ci-yml/Python.gitlab-ci.yml23
-rw-r--r--yarn.lock6
206 files changed, 2304 insertions, 1465 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2249115e82a..71ddebef662 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -364,10 +364,11 @@ update-tests-metadata:
- rspec_flaky/
policy: push
script:
- - retry gem install fog-aws mime-types
+ - retry gem install fog-aws mime-types activesupport
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
+ - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index fcdb2e109f6..ee74734aa22 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-4.0.0
+4.1.0
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 03fe5f2ed26..c953b9708a0 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -321,6 +321,9 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
+ goldiloader (2.0.1)
+ activerecord (>= 4.2, < 5.2)
+ activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
@@ -878,7 +881,7 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-notifier (1.5.1)
- spinach (0.10.1)
+ spinach (0.8.10)
colorize
gherkin-ruby (>= 0.3.2)
json
@@ -1072,6 +1075,7 @@ DEPENDENCIES
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
+ goldiloader (~> 2.0)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 6aa44ca2c11..711bafa17a9 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -22,7 +22,7 @@ export default {
...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
...mapGetters(['currentMergeRequest']),
shouldHideEditor() {
- return this.file && this.file.binary && !this.file.raw;
+ return this.file && this.file.binary && !this.file.content;
},
editTabCSS() {
return {
@@ -212,7 +212,7 @@ export default {
<content-viewer
v-if="shouldHideEditor || file.viewMode === 'preview'"
:content="file.content || file.raw"
- :path="file.rawPath"
+ :path="file.rawPath || file.path"
:file-size="file.size"
:project-path="file.projectId"/>
</div>
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index ac70ddb3ff4..b0573510ff9 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 476b15aca4a..e0f883a8e08 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -258,7 +258,9 @@ Please check your network connection and try again.`;
:key="note.id"
/>
</ul>
- <div class="discussion-reply-holder">
+ <div
+ :class="{ 'is-replying': isReplying }"
+ class="discussion-reply-holder">
<template v-if="!isReplying && canReply">
<div
class="btn-group-justified discussion-with-resolve-btn"
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index be37df36be8..628913483c6 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -1,12 +1,12 @@
import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
-import ProjectNew from '../shared/project_new';
+import initProjectLoadingSpinner from '../shared/save_project_loader';
import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
- new ProjectNew(); // eslint-disable-line no-new
+ initProjectLoadingSpinner();
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js
index ea6fd961393..7db644e2477 100644
--- a/app/assets/javascripts/pages/projects/new/index.js
+++ b/app/assets/javascripts/pages/projects/new/index.js
@@ -1,9 +1,9 @@
-import ProjectNew from '../shared/project_new';
+import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new';
document.addEventListener('DOMContentLoaded', () => {
- new ProjectNew(); // eslint-disable-line no-new
+ initProjectLoadingSpinner();
initProjectVisibilitySelector();
initProjectNew.bindEvents();
});
diff --git a/app/assets/javascripts/pages/projects/shared/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js
deleted file mode 100644
index 56d5574aa2f..00000000000
--- a/app/assets/javascripts/pages/projects/shared/project_new.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
-
-import $ from 'jquery';
-import VisibilitySelect from '../../../visibility_select';
-
-function highlightChanges($elm) {
- $elm.addClass('highlight-changes');
- setTimeout(() => $elm.removeClass('highlight-changes'), 10);
-}
-
-export default class ProjectNew {
- constructor() {
- this.toggleSettings = this.toggleSettings.bind(this);
- this.$selects = $('.features select');
- this.$repoSelects = this.$selects.filter('.js-repo-select');
- this.$projectSelects = this.$selects.not('.js-repo-select');
-
- $('.project-edit-container').on('ajax:before', () => {
- $('.project-edit-container').hide();
- return $('.save-project-loader').show();
- });
-
- this.initVisibilitySelect();
-
- this.toggleSettings();
- this.toggleSettingsOnclick();
- this.toggleRepoVisibility();
- }
-
- initVisibilitySelect() {
- const visibilityContainer = document.querySelector('.js-visibility-select');
- if (!visibilityContainer) return;
- const visibilitySelect = new VisibilitySelect(visibilityContainer);
- visibilitySelect.init();
-
- const $visibilitySelect = $(visibilityContainer).find('select');
- let projectVisibility = $visibilitySelect.val();
- const PROJECT_VISIBILITY_PRIVATE = '0';
-
- $visibilitySelect.on('change', () => {
- const newProjectVisibility = $visibilitySelect.val();
-
- if (projectVisibility !== newProjectVisibility) {
- this.$projectSelects.each((idx, select) => {
- const $select = $(select);
- const $options = $select.find('option');
- const values = $.map($options, e => e.value);
-
- // if switched to "private", limit visibility options
- if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
- if ($select.val() !== values[0] && $select.val() !== values[1]) {
- $select.val(values[1]).trigger('change');
- highlightChanges($select);
- }
- $options.slice(2).disable();
- }
-
- // if switched from "private", increase visibility for non-disabled options
- if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
- $options.enable();
- if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
- $select.val(values[values.length - 1]).trigger('change');
- highlightChanges($select);
- }
- }
- });
-
- projectVisibility = newProjectVisibility;
- }
- });
- }
-
- toggleSettings() {
- this.$selects.each(function () {
- var $select = $(this);
- var className = $select.data('field')
- .replace(/_/g, '-')
- .replace('access-level', 'feature');
- ProjectNew._showOrHide($select, '.' + className);
- });
- }
-
- toggleSettingsOnclick() {
- this.$selects.on('change', this.toggleSettings);
- }
-
- static _showOrHide(checkElement, container) {
- const $container = $(container);
-
- if ($(checkElement).val() !== '0') {
- return $container.show();
- }
- return $container.hide();
- }
-
- toggleRepoVisibility() {
- var $repoAccessLevel = $('.js-repo-access-level select');
- var $lfsEnabledOption = $('.js-lfs-enabled select');
- var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
- var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
- var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
-
- this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
- .nextAll()
- .hide();
-
- $repoAccessLevel
- .off('change')
- .on('change', function () {
- var selectedVal = parseInt($repoAccessLevel.val(), 10);
-
- this.$repoSelects.each(function () {
- var $this = $(this);
- var repoSelectVal = parseInt($this.val(), 10);
-
- $this.find('option').enable();
-
- if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
- $this.val(selectedVal).trigger('change');
- highlightChanges($this);
- }
-
- $this.find("option[value='" + selectedVal + "']").nextAll().disable();
- });
-
- if (selectedVal) {
- this.$repoSelects.removeClass('disabled');
-
- if ($lfsEnabledOption.length) {
- $lfsEnabledOption.removeClass('disabled');
- highlightChanges($lfsEnabledOption);
- }
- if (containerRegistry) {
- containerRegistry.style.display = '';
- }
- } else {
- this.$repoSelects.addClass('disabled');
-
- if ($lfsEnabledOption.length) {
- $lfsEnabledOption.val('false').addClass('disabled');
- highlightChanges($lfsEnabledOption);
- }
- if (containerRegistry) {
- containerRegistry.style.display = 'none';
- containerRegistryCheckbox.checked = false;
- }
- }
-
- prevSelectedVal = selectedVal;
- }.bind(this));
- }
-}
diff --git a/app/assets/javascripts/pages/projects/shared/save_project_loader.js b/app/assets/javascripts/pages/projects/shared/save_project_loader.js
new file mode 100644
index 00000000000..aa3589ac88d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/save_project_loader.js
@@ -0,0 +1,12 @@
+import $ from 'jquery';
+
+export default function initProjectLoadingSpinner() {
+ const $formContainer = $('.project-edit-container');
+ const $loadingSpinner = $('.save-project-loader');
+
+ // show loading spinner when saving
+ $formContainer.on('ajax:before', () => {
+ $formContainer.hide();
+ $loadingSpinner.show();
+ });
+}
diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
index 08f0afdcce3..d321892d2d2 100644
--- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
@@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
* Does that setting the current selected tab in the localStorage
*/
export default class SigninTabsMemoizer {
- constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+ constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 8bc7a1f20b2..b3fcaf0ccd1 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -13,16 +13,16 @@
* 3. Merge request widget
* 4. Commit widget
*/
-
+ import axios from '../../lib/utils/axios_utils';
import Flash from '../../flash';
- import icon from '../../vue_shared/components/icon.vue';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+ import Icon from '../../vue_shared/components/icon.vue';
+ import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
- loadingIcon,
- icon,
+ LoadingIcon,
+ Icon,
},
directives: {
@@ -88,9 +88,8 @@
},
fetchJobs() {
- this.$http.get(this.stage.dropdown_path)
- .then(response => response.json())
- .then((data) => {
+ axios.get(this.stage.dropdown_path)
+ .then(({ data }) => {
this.dropdownContent = data.html;
this.isLoading = false;
})
@@ -98,8 +97,7 @@
this.closeDropdown();
this.isLoading = false;
- const flash = new Flash('Something went wrong on our end.');
- return flash;
+ Flash('Something went wrong on our end.');
});
},
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index 621969cd622..5633e54b28a 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -40,10 +40,8 @@ export default class pipelinesMediator {
}
successCallback(response) {
- return response.json().then((data) => {
- this.state.isLoading = false;
- this.store.storePipeline(data);
- });
+ this.state.isLoading = false;
+ this.store.storePipeline(response.data);
}
errorCallback() {
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
index 3e0c52c7726..a53a9cc8365 100644
--- a/app/assets/javascripts/pipelines/services/pipeline_service.js
+++ b/app/assets/javascripts/pipelines/services/pipeline_service.js
@@ -1,19 +1,16 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-Vue.use(VueResource);
+import axios from '../../lib/utils/axios_utils';
export default class PipelineService {
constructor(endpoint) {
- this.pipeline = Vue.resource(endpoint);
+ this.pipeline = endpoint;
}
getPipeline() {
- return this.pipeline.get();
+ return axios.get(this.pipeline);
}
- // eslint-disable-next-line
+ // eslint-disable-next-line class-methods-use-this
postAction(endpoint) {
- return Vue.http.post(`${endpoint}.json`);
+ return axios.post(`${endpoint}.json`);
}
}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
deleted file mode 100644
index 2d324c71379..00000000000
--- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export default {
- name: 'time-tracking-estimate-only-pane',
- props: {
- timeEstimateHumanReadable: {
- type: String,
- required: true,
- },
- },
- template: `
- <div class="time-tracking-estimate-only-pane">
- <span class="bold">
- {{ s__('TimeTracking|Estimated:') }}
- </span>
- {{ timeEstimateHumanReadable }}
- </div>
- `,
-};
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
new file mode 100644
index 00000000000..08fce597e50
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue
@@ -0,0 +1,20 @@
+<script>
+export default {
+ name: 'TimeTrackingEstimateOnlyPane',
+ props: {
+ timeEstimateHumanReadable: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="time-tracking-estimate-only-pane">
+ <span class="bold">
+ {{ s__('TimeTracking|Estimated:') }}
+ </span>
+ {{ timeEstimateHumanReadable }}
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
index 19f74ad3c6d..825063d9ba6 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue
@@ -1,7 +1,8 @@
+<script>
import { sprintf, s__ } from '../../../locale';
export default {
- name: 'time-tracking-help-state',
+ name: 'TimeTrackingHelpState',
props: {
rootPath: {
type: String,
@@ -27,26 +28,28 @@ export default {
);
},
},
- template: `
- <div class="time-tracking-help-state">
- <div class="time-tracking-info">
- <h4>
- {{ __('Track time with quick actions') }}
- </h4>
- <p>
- {{ __('Quick actions can be used in the issues description and comment boxes.') }}
- </p>
- <p v-html="estimateText">
- </p>
- <p v-html="spendText">
- </p>
- <a
- class="btn btn-default learn-more-button"
- :href="href"
- >
- {{ __('Learn more') }}
- </a>
- </div>
- </div>
- `,
};
+</script>
+
+<template>
+ <div class="time-tracking-help-state">
+ <div class="time-tracking-info">
+ <h4>
+ {{ __('Track time with quick actions') }}
+ </h4>
+ <p>
+ {{ __('Quick actions can be used in the issues description and comment boxes.') }}
+ </p>
+ <p v-html="estimateText">
+ </p>
+ <p v-html="spendText">
+ </p>
+ <a
+ class="btn btn-default learn-more-button"
+ :href="href"
+ >
+ {{ __('Learn more') }}
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 1c641c73ea3..71dca498b3d 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -1,9 +1,9 @@
<script>
-import timeTrackingHelpState from './help_state';
+import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane';
-import timeTrackingEstimateOnlyPane from './estimate_only_pane';
+import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
import TimeTrackingComparisonPane from './comparison_pane.vue';
import eventHub from '../../event_hub';
@@ -12,11 +12,11 @@ export default {
name: 'IssuableTimeTracker',
components: {
TimeTrackingCollapsedState,
- 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
+ TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
TimeTrackingComparisonPane,
- 'time-tracking-help-state': timeTrackingHelpState,
+ TimeTrackingHelpState,
},
props: {
time_estimate: {
diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js
deleted file mode 100644
index 0c928d0d5f6..00000000000
--- a/app/assets/javascripts/visibility_select.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export default class VisibilitySelect {
- constructor(container) {
- if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
- this.container = container;
- this.helpBlock = this.container.querySelector('.help-block');
- this.select = this.container.querySelector('select');
- }
-
- init() {
- if (this.select) {
- this.updateHelpText();
- this.select.addEventListener('change', this.updateHelpText.bind(this));
- } else {
- this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
- }
- }
-
- updateHelpText() {
- this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
- }
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
index 9ade6a91747..a1f7e696795 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
@@ -7,7 +7,10 @@ export default {
statusIcon,
},
props: {
- mr: { type: Object, required: true },
+ mr: {
+ type: Object,
+ required: true,
+ },
},
};
</script>
@@ -20,13 +23,14 @@ export default {
/>
<div class="media-body space-children">
<span class="bold">
- There are unresolved discussions. Please resolve these discussions
+ {{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }}
</span>
<a
v-if="mr.createIssueToResolveDiscussionsPath"
:href="mr.createIssueToResolveDiscussionsPath"
- class="btn btn-default btn-xs js-create-issue">
- Create an issue to resolve them later
+ class="btn btn-default btn-xs js-create-issue"
+ >
+ {{ s__("mrWidget|Create an issue to resolve them later") }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index d91fe3cf0c5..db453c30576 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -27,20 +27,22 @@
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
methods: {
- isMarkdownForm(form) {
- return form && !form.find('.js-vue-markdown-field').length;
+ isValid(form) {
+ return !form ||
+ form.find('.js-vue-markdown-field').length ||
+ $(this.$el).closest('form') === form[0];
},
previewMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
- if (this.isMarkdownForm(form)) return;
+ if (!this.isValid(form)) return;
this.$emit('preview-markdown');
},
writeMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
- if (this.isMarkdownForm(form)) return;
+ if (!this.isValid(form)) return;
this.$emit('write-markdown');
},
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 679f783b1b6..7f037582ca0 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -813,7 +813,6 @@
}
.discussion-notes {
- padding: 0 $gl-padding $gl-padding;
min-height: 35px;
&:first-child {
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index b2250a1ce2f..97303d02666 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -154,26 +154,10 @@
a {
width: 100%;
font-size: 18px;
- margin-right: 0;
-
- &:hover {
- border: 1px solid transparent;
- }
}
- &.active {
- border-bottom: 1px solid $border-color;
-
- a {
- border: 0;
- border-bottom: 2px solid $link-underline-blue;
- margin-right: 0;
- color: $black;
-
- &:hover {
- border-bottom: 2px solid $link-underline-blue;
- }
- }
+ &.active > a {
+ cursor: default;
}
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 8720f821ce9..4a528bc2bb1 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -173,7 +173,11 @@
}
.discussion-form {
- padding-top: $gl-padding-top;
+ background-color: $white-light;
+}
+
+.discussion-form-container {
+ padding: $gl-padding-top $gl-padding $gl-padding;
}
.discussion-notes .disabled-comment {
@@ -233,7 +237,12 @@
.discussion-body,
.diff-file {
.discussion-reply-holder {
- padding-top: $gl-padding;
+ background-color: $white-light;
+ padding: 10px 16px;
+
+ &.is-replying {
+ padding-bottom: $gl-padding;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 9d9cbecc958..81e98f358a8 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -47,7 +47,7 @@ ul.notes {
}
.timeline-entry-inner {
- padding: $gl-padding 0;
+ padding: $gl-padding $gl-btn-padding;
border-bottom: 1px solid $white-normal;
}
@@ -94,6 +94,12 @@ ul.notes {
}
}
+ &.note-discussion {
+ .timeline-entry-inner {
+ padding: $gl-padding 10px;
+ }
+ }
+
.editing-spinner {
display: none;
}
@@ -346,8 +352,6 @@ ul.notes {
}
.discussion-notes {
- background-color: $white-light;
-
&:not(:first-child) {
border-top: 1px solid $white-normal;
margin-top: 20px;
@@ -359,6 +363,10 @@ ul.notes {
}
}
+ .notes {
+ background-color: $white-light;
+ }
+
a code {
top: 0;
margin-right: 0;
@@ -639,6 +647,8 @@ ul.notes {
border-bottom: 1px solid $white-normal;
.timeline-entry-inner {
+ padding-left: $gl-padding;
+ padding-right: $gl-padding;
border-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 8d5eb2e8c5a..2c840cb407a 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -344,7 +344,6 @@
svg {
vertical-align: middle;
- margin-right: 3px;
}
.stage-column {
@@ -495,17 +494,12 @@
svg {
fill: $gl-text-color-secondary;
position: relative;
- left: 1px;
top: -1px;
- width: 16px;
- height: 16px;
}
&.play {
svg {
- width: 16px;
- height: 16px;
- left: 3px;
+ left: 2px;
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 790e91e4431..d7d343b088a 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -935,11 +935,6 @@ pre.light-well {
}
}
- .dropdown-menu-toggle {
- width: 100%;
- max-width: 300px;
- }
-
.flash-container {
padding: 0;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4dfb397e82c..145f74d9e59 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -56,7 +56,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def application_setting_params
+ params[:application_setting] ||= {}
import_sources = params[:application_setting][:import_sources]
+
if import_sources.nil?
params[:application_setting][:import_sources] = []
else
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 839cac3687c..ad4e936a3d4 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -41,7 +41,7 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note)
- Notes::RenderService.new(current_user).execute([@note], @project)
+ Notes::RenderService.new(current_user).execute([@note])
end
respond_to do |format|
@@ -56,7 +56,7 @@ module NotesActions
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
- Notes::RenderService.new(current_user).execute([@note], @project)
+ Notes::RenderService.new(current_user).execute([@note])
end
respond_to do |format|
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index e7ef297879f..36e3d76ecfe 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -4,7 +4,7 @@ module RendersNotes
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes)
- Notes::RenderService.new(current_user).execute(notes, @project)
+ Notes::RenderService.new(current_user).execute(notes)
notes
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 283c3e5f1e0..5ac4b8710e2 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
- Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
+ Events::RenderService
+ .new(current_user)
+ .execute(@events, atom_request: request.format.atom?)
end
def user_actions
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index effb484ef0f..b7f548e0e63 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController
def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
+ @pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
respond_to do |format|
format.html
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index dd41b9648e8..86c50d88a2a 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
private
def render_json_with_notes_serializer
- Notes::RenderService.new(current_user).execute([note], project)
+ Notes::RenderService.new(current_user).execute([note])
render json: note_serializer.represent(note)
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 61c72aa22a8..7ed9b1fc6d0 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -159,7 +159,10 @@ class IssuableFinder
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else
- ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
+ opts = { current_user: current_user }
+ opts[:project_ids_relation] = item_project_ids(items) if items
+
+ ProjectsFinder.new(opts).execute
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
@@ -316,9 +319,9 @@ class IssuableFinder
def by_project(items)
items =
if project?
- items.of_projects(projects(items)).references_project
- elsif projects(items)
- items.merge(projects(items).reorder(nil)).join_project
+ items.of_projects(projects).references_project
+ elsif projects
+ items.merge(projects.reorder(nil)).join_project
else
items.none
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 636316da80a..f0afcac5986 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -94,7 +94,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project
- path = pipelines_project_commit_path(project, pipeline_status.sha)
+ path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
render_status_with_link(
'commit',
@@ -105,7 +105,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
- path = pipelines_project_commit_path(project, commit)
+ path = pipelines_project_commit_path(project, commit, ref: ref)
render_status_with_link(
'commit',
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 2fe1927a189..39e7a7fd396 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -256,7 +256,7 @@ module MarkupHelper
return '' unless html.present?
context.merge!(
- current_user: (current_user if defined?(current_user)),
+ current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
commit: @commit,
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 15f48e43a28..a64b2acdd77 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -157,40 +157,6 @@ module ProjectsHelper
current_user&.recent_push(@project)
end
- def project_feature_access_select(field)
- # Don't show option "everyone with access" if project is private
- options = project_feature_options
-
- level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
-
- if @project.private?
- disabled_option = ProjectFeature::ENABLED
- highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
- end
-
- options = options_for_select(
- options.invert,
- selected: highest_available_option || level,
- disabled: disabled_option
- )
-
- content_tag :div, class: "select-wrapper" do
- concat(
- content_tag(
- :select,
- options,
- name: "project[project_feature_attributes][#{field}]",
- id: "project_project_feature_attributes_#{field}",
- class: "pull-right form-control select-control #{repo_children_classes(field)} ",
- data: { field: field }
- )
- )
- concat(
- icon('chevron-down')
- )
- end.html_safe
- end
-
def link_to_autodeploy_doc
link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
end
@@ -274,16 +240,6 @@ module ProjectsHelper
private
- def repo_children_classes(field)
- needs_repo_check = [:merge_requests_access_level, :builds_access_level]
- return unless needs_repo_check.include?(field)
-
- classes = "project-repo-select js-repo-select"
- classes << " disabled" unless @project.feature_available?(:repository, current_user)
-
- classes
- end
-
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
@@ -447,14 +403,6 @@ module ProjectsHelper
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end
- def project_feature_options
- {
- ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'),
- ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'),
- ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access')
- }
- end
-
def project_child_container_class(view_path)
view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
end
@@ -463,20 +411,6 @@ module ProjectsHelper
IssuesFinder.new(current_user, project_id: project.id).execute
end
- def visibility_select_options(project, selected_level)
- level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options|
- next if restricted_levels.include?(level)
-
- level_options << [
- visibility_level_label(level),
- { data: { description: visibility_level_description(level, project) } },
- level
- ]
- end
-
- options_for_select(level_options, selected_level)
- end
-
def restricted_levels
return [] if current_user.admin?
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 0b561203914..4aa236555cb 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base
after_commit :flush_redis_cache
def self.current
- messages = Rails.cache.fetch(CACHE_KEY) { current_and_future_messages.to_a }
+ messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a }
return messages if messages.empty?
@@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base
where('ends_at > :now', now: Time.zone.now).order_id_asc
end
+ def self.cache_expires_in
+ nil
+ end
+
def active?
started? && !ended?
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 3805f6cf857..741a84194e2 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -110,7 +110,10 @@ class Event < ActiveRecord::Base
end
end
+ # Remove this method when removing Gitlab.rails5? code.
def subclass_from_attributes(attrs)
+ return super if Gitlab.rails5?
+
# Without this Rails will keep calling this method on the returned class,
# resulting in an infinite loop.
return unless self == Event
diff --git a/app/services/events/render_service.rb b/app/services/events/render_service.rb
index 0b62d8aedf1..bb72d7685dd 100644
--- a/app/services/events/render_service.rb
+++ b/app/services/events/render_service.rb
@@ -1,15 +1,17 @@
module Events
class RenderService < BaseRenderer
def execute(events, atom_request: false)
- events.map(&:note).compact.group_by(&:project).each do |project, notes|
- render_notes(notes, project, atom_request)
- end
+ notes = events.map(&:note).compact
+
+ render_notes(notes, atom_request)
end
private
- def render_notes(notes, project, atom_request)
- Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request))
+ def render_notes(notes, atom_request)
+ Notes::RenderService
+ .new(current_user)
+ .execute(notes, render_options(atom_request))
end
def render_options(atom_request)
diff --git a/app/services/notes/render_service.rb b/app/services/notes/render_service.rb
index a77e98c2b07..efc9d6da2aa 100644
--- a/app/services/notes/render_service.rb
+++ b/app/services/notes/render_service.rb
@@ -3,19 +3,18 @@ module Notes
# Renders a collection of Note instances.
#
# notes - The notes to render.
- # project - The project to use for redacting.
- # user - The user viewing the notes.
-
+ #
# Possible options:
+ #
# requested_path - The request path.
# project_wiki - The project's wiki.
# ref - The current Git reference.
# only_path - flag to turn relative paths into absolute ones.
# xhtml - flag to save the html in XHTML
- def execute(notes, project, **opts)
- renderer = Banzai::ObjectRenderer.new(project, current_user, **opts)
-
- renderer.render(notes, :note)
+ def execute(notes, options = {})
+ Banzai::ObjectRenderer
+ .new(user: current_user, redaction_context: options)
+ .render(notes, :note)
end
end
end
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index bbfeceff5b9..2ff4221efbd 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -33,7 +33,7 @@
= link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
- if user.access_locked?
%li
- = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
+ = link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
- if can?(current_user, :destroy_user, user)
%li.divider
- if user.can_be_removed?
diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml
index f943d25e41a..7bd414d64c3 100644
--- a/app/views/devise/shared/_tab_single.html.haml
+++ b/app/views/devise/shared/_tab_single.html.haml
@@ -1,3 +1,3 @@
-%ul.nav-links.nav-tabs.new-session-tabs.single-tab
+%ul.nav-links.new-session-tabs.single-tab
%li.active
%a= tab_title
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index 270191f9452..f50e0724e09 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -1,4 +1,4 @@
-%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
+%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
- if crowd_enabled?
%li.active
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index 1ba6d390875..fa3c3df7f60 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
+%ul.nav-links.new-session-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if allow_signup?
diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml
deleted file mode 100644
index 4026b9e3c46..00000000000
--- a/app/views/projects/_visibility_select.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- if can_change_visibility_level?(@project, current_user)
- .select-wrapper
- = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
- = icon('chevron-down')
-- else
- .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
- = visibility_level_icon(@project.visibility_level)
- %strong
- = visibility_level_label(@project.visibility_level)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 163432c9263..289bfdd69bc 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -5,6 +5,7 @@
- link = commit_path(project, commit, merge_request: merge_request)
- cache_key = [project.full_path,
+ ref,
commit.id,
Gitlab::CurrentSettings.current_application_settings,
@path.presence,
@@ -54,7 +55,7 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
+ .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
.commit-sha-group
.label.label-monospace
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 8beb4ffef45..cbbcc8f1db5 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -55,7 +55,7 @@
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- - if @build.has_trace?
+ - if @build.running? || @build.has_trace?
.build-trace-container.prepend-top-default
.top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden-xs.pull-left.hidden<
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index a09c13176c3..300055a4207 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,4 +1,4 @@
-.panel.panel-default.protected-branches-list.js-protected-branches-list
+.protected-branches-list.js-protected-branches-list
- if @protected_branches.empty?
.panel-heading
%h3.panel-title
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index 02908e16dc5..3ed82e51dbe 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,4 +1,4 @@
-.panel.panel-default.protected-tags-list.js-protected-tags-list
+.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.panel-heading
%h3.panel-title
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 725bf916592..71c0d740bc8 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -24,20 +24,21 @@
-# DiffNote
= f.hidden_field :position
- = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
- = render 'projects/zen', f: f,
- attr: :note,
- classes: 'note-textarea js-note-text',
- placeholder: "Write a comment or drag your files here...",
- supports_quick_actions: supports_quick_actions,
- supports_autocomplete: supports_autocomplete
- = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
- .error-alert
-
- .note-form-actions.clearfix
- = render partial: 'shared/notes/comment_button'
-
- = yield(:note_actions)
-
- %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
- Discard draft
+ .discussion-form-container
+ = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
+ = render 'projects/zen', f: f,
+ attr: :note,
+ classes: 'note-textarea js-note-text',
+ placeholder: "Write a comment or drag your files here...",
+ supports_quick_actions: supports_quick_actions,
+ supports_autocomplete: supports_autocomplete
+ = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
+ .error-alert
+
+ .note-form-actions.clearfix
+ = render partial: 'shared/notes/comment_button'
+
+ = yield(:note_actions)
+
+ %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
+ Discard draft
diff --git a/bin/spinach b/bin/spinach
index 474050e29d1..eda81c9ed8a 100755
--- a/bin/spinach
+++ b/bin/spinach
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+
+# Remove this block when removing rails5? code.
+gemfile = %w[1 true].include?(ENV["RAILS5"]) ? "Gemfile.rails5" : "Gemfile"
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
+
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
diff --git a/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml
new file mode 100644
index 00000000000..ea007670332
--- /dev/null
+++ b/changelogs/unreleased/41436-use-simpler-env-vars-for-auto-devops-replicas.yml
@@ -0,0 +1,5 @@
+---
+title: 'Introduce simpler env vars for auto devops REPLICAS and CANARY_REPLICAS #41436'
+merge_request: 18036
+author:
+type: added
diff --git a/changelogs/unreleased/41748-vertical-misalignment-login-box.yml b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml
new file mode 100644
index 00000000000..77a97400323
--- /dev/null
+++ b/changelogs/unreleased/41748-vertical-misalignment-login-box.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor CSS to eliminate vertical misalignment of login nav
+merge_request: 16275
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml
new file mode 100644
index 00000000000..ff734fe0c05
--- /dev/null
+++ b/changelogs/unreleased/44541-fix-file-tree-commit-status-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipeline status in branch/tag tree page
+merge_request: 17995
+author:
+type: fixed
diff --git a/changelogs/unreleased/44697-prevue.yml b/changelogs/unreleased/44697-prevue.yml
new file mode 100644
index 00000000000..9fdce5869ae
--- /dev/null
+++ b/changelogs/unreleased/44697-prevue.yml
@@ -0,0 +1,5 @@
+---
+title: Make toggle markdown preview shortcut only toggle selected field
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml b/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml
deleted file mode 100644
index 53e4ebdb996..00000000000
--- a/changelogs/unreleased/44870-remove-extra-space-around-comment-form-on-merge-requests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refactor and tweak margin for note forms on Issuable
-merge_request: 18120
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/45159-fix-illustration.yml b/changelogs/unreleased/45159-fix-illustration.yml
new file mode 100644
index 00000000000..3b9cb45b916
--- /dev/null
+++ b/changelogs/unreleased/45159-fix-illustration.yml
@@ -0,0 +1,5 @@
+---
+title: Adds illustration for when job log was erased
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/45287-align-icons.yml b/changelogs/unreleased/45287-align-icons.yml
new file mode 100644
index 00000000000..0a1cccf9ca6
--- /dev/null
+++ b/changelogs/unreleased/45287-align-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Align action icons in pipeline graph
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/ab-37125-assigned-issues-query.yml b/changelogs/unreleased/ab-37125-assigned-issues-query.yml
new file mode 100644
index 00000000000..5d4aad08764
--- /dev/null
+++ b/changelogs/unreleased/ab-37125-assigned-issues-query.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce complexity of issuable finder query.
+merge_request: 18219
+author:
+type: performance
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml
new file mode 100644
index 00000000000..bcfba4ae70d
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-commits-branches-feature.yml
@@ -0,0 +1,5 @@
+---
+title: "Replace the `project/commits/branches.feature` spinach test with an rspec analog"
+merge_request: 18302
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml
new file mode 100644
index 00000000000..0dcac0a80eb
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-replace-spinach-project-issues-milestones-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Replace the `project/issues/milestones.feature` spinach test with an rspec analog
+merge_request: 18300
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/fix-wiki-find-file-gitaly.yml b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml
new file mode 100644
index 00000000000..5c536be7ae5
--- /dev/null
+++ b/changelogs/unreleased/fix-wiki-find-file-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Fix finding wiki file when Gitaly is enabled
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fl-pipelines-details-axios.yml b/changelogs/unreleased/fl-pipelines-details-axios.yml
new file mode 100644
index 00000000000..0b72e54cba3
--- /dev/null
+++ b/changelogs/unreleased/fl-pipelines-details-axios.yml
@@ -0,0 +1,5 @@
+---
+title: Replace vue resource with axios for pipelines details page
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/move-estimate-only-pane-vue-component.yml b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml
new file mode 100644
index 00000000000..b6c538f70b3
--- /dev/null
+++ b/changelogs/unreleased/move-estimate-only-pane-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingEstimateOnlyPane vue component
+merge_request: 18318
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/move-help-state-vue-component.yml b/changelogs/unreleased/move-help-state-vue-component.yml
new file mode 100644
index 00000000000..6108368cde0
--- /dev/null
+++ b/changelogs/unreleased/move-help-state-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move TimeTrackingHelpState vue component
+merge_request: 18319
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/rendering-markdown-multiple-projects.yml b/changelogs/unreleased/rendering-markdown-multiple-projects.yml
new file mode 100644
index 00000000000..8685772c089
--- /dev/null
+++ b/changelogs/unreleased/rendering-markdown-multiple-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Support Markdown rendering using multiple projects
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml
new file mode 100644
index 00000000000..d99a9c93c0b
--- /dev/null
+++ b/changelogs/unreleased/unresolved-discussions-vue-component-i18n-and-tests.yml
@@ -0,0 +1,5 @@
+---
+title: Add i18n and update specs for UnresolvedDiscussions vue component
+merge_request: 17866
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/winh-dropdown-entry-unlocking.yml b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml
new file mode 100644
index 00000000000..fc669af1f57
--- /dev/null
+++ b/changelogs/unreleased/winh-dropdown-entry-unlocking.yml
@@ -0,0 +1,5 @@
+---
+title: Remove green background from unlock button in admin area
+merge_request: 18288
+author:
+type: changed
diff --git a/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml
new file mode 100644
index 00000000000..3d11ee588ae
--- /dev/null
+++ b/changelogs/unreleased/zj-branch-containing-sha-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detecting branchnames containing a commit uses Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml
new file mode 100644
index 00000000000..4774c7811d1
--- /dev/null
+++ b/changelogs/unreleased/zj-tag-containing-sha-opt-out.yml
@@ -0,0 +1,5 @@
+---
+title: Detecting tags containing a commit uses Gitaly by default
+merge_request:
+author:
+type: performance
diff --git a/config/karma.config.js b/config/karma.config.js
index c378e621953..61f02294157 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -1,5 +1,6 @@
var path = require('path');
var webpack = require('webpack');
+var argumentsParser = require('commander');
var webpackConfig = require('./webpack.config.js');
var ROOT_PATH = path.resolve(__dirname, '..');
@@ -14,6 +15,24 @@ if (webpackConfig.plugins) {
});
}
+var testFiles = argumentsParser
+ .option(
+ '-f, --filter-spec [filter]',
+ 'Filter run spec files by path. Multiple filters are like a logical OR.',
+ (val, memo) => {
+ memo.push(val);
+ return memo;
+ },
+ []
+ )
+ .parse(process.argv).filterSpec;
+
+webpackConfig.plugins.push(
+ new webpack.DefinePlugin({
+ 'process.env.TEST_FILES': JSON.stringify(testFiles),
+ })
+);
+
webpackConfig.devtool = 'cheap-inline-source-map';
// Karma configuration
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index dfb50c195c1..1e260236dc5 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -1,14 +1,14 @@
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
- User.seed do |s|
- s.id = 1
- s.name = 'Administrator'
- s.email = 'admin@example.com'
- s.notification_email = 'admin@example.com'
- s.username = 'root'
- s.password = '5iveL!fe'
- s.admin = true
- s.confirmed_at = DateTime.now
- end
+ User.create!(
+ name: 'Administrator',
+ email: 'admin@example.com',
+ username: 'root',
+ password: '5iveL!fe',
+ admin: true,
+ confirmed_at: DateTime.now
+ )
+
+ print '.'
end
diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb
index c1bbc9af6d6..00a14f458d1 100644
--- a/db/fixtures/development/19_environments.rb
+++ b/db/fixtures/development/19_environments.rb
@@ -28,7 +28,11 @@ class Gitlab::Seeder::Environments
end
def create_merge_request_review_deployments!
- @project.merge_requests.sample(4).map do |merge_request|
+ @project
+ .merge_requests
+ .select { |mr| mr.source_branch.match(/\p{Alnum}+/) }
+ .sample(4)
+ .each do |merge_request|
next unless merge_request.diff_head_sha
create_deployment!(
diff --git a/doc/README.md b/doc/README.md
index 604f7244a34..178e6567845 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -159,8 +159,12 @@ applications are always responsive and available. GitLab collects and displays
performance metrics for deployed apps using Prometheus so you can know in an
instant how code changes impact your production environment.
+- [GitLab Prometheus](administration/monitoring/prometheus/index.md): Configure the bundled Prometheus to collect various metrics from your GitLab instance.
+- [Prometheus project integration](user/project/integrations/prometheus.md): Configure the Prometheus integration per project and monitor your CI/CD environments.
+- [Prometheus metrics](user/project/integrations/prometheus_library/metrics.md): Let Prometheus collect metrics from various services, like Kubernetes, NGINX, NGINX ingress controller, HAProxy, and Amazon Cloud Watch.
+- [GitLab Performance Monitoring](administration/monitoring/performance/index.md): Use InfluxDB and Grafana to monitor the performance of your GitLab instance (will be eventually replaced by Prometheus).
+- [Health check](user/admin_area/monitoring/health_check.md): GitLab provides liveness and readiness probes to indicate service health and reachability to required services.
- [GitLab Cycle Analytics](user/project/cycle_analytics.md): Cycle Analytics measures the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have.
-- [GitLab Performance Monitoring](administration/monitoring/performance/index.md)
## Getting started with GitLab
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 68aa64b3834..bd7ffb2befb 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -869,37 +869,29 @@ skip the download step.
- Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
- Windows support was added in GitLab Runner v.1.0.0.
- From GitLab 9.2, caches are restored before artifacts.
-- Currently not all executors are supported.
+- Not all executors are [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart).
- Job artifacts are only collected for successful jobs by default.
`artifacts` is used to specify a list of files and directories which should be
-attached to the job after success. You can only use paths that are within the
-project workspace. To pass artifacts between different jobs, see [dependencies](#dependencies).
-Below are some examples.
+attached to the job after success.
-Send all files in `binaries` and `.config`:
+The artifacts will be sent to GitLab after the job finishes successfully and will
+be available for download in the GitLab UI.
-```yaml
-artifacts:
- paths:
- - binaries/
- - .config
-```
+[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md)
-Send all Git untracked files:
+### `artifacts:paths`
-```yaml
-artifacts:
- untracked: true
-```
+You can only use paths that are within the project workspace. To pass artifacts
+between different jobs, see [dependencies](#dependencies).
-Send all Git untracked files and files in `binaries`:
+Send all files in `binaries` and `.config`:
```yaml
artifacts:
- untracked: true
paths:
- binaries/
+ - .config
```
To disable artifact passing, define the job with empty [dependencies](#dependencies):
@@ -933,11 +925,6 @@ release-job:
- tags
```
-The artifacts will be sent to GitLab after the job finishes successfully and will
-be available for download in the GitLab UI.
-
-[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md)
-
### `artifacts:name`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
@@ -954,26 +941,30 @@ To create an archive with a name of the current job:
job:
artifacts:
name: "$CI_JOB_NAME"
+ paths:
+ - binaries/
```
To create an archive with a name of the current branch or tag including only
-the files that are untracked by Git:
+the binaries directory:
```yaml
job:
artifacts:
name: "$CI_COMMIT_REF_NAME"
- untracked: true
+ paths:
+ - binaries/
```
To create an archive with a name of the current job and the current branch or
-tag including only the files that are untracked by Git:
+tag including only the binaries directory:
```yaml
job:
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
- untracked: true
+ paths:
+ - binaries/
```
To create an archive with a name of the current [stage](#stages) and branch name:
@@ -982,7 +973,8 @@ To create an archive with a name of the current [stage](#stages) and branch name
job:
artifacts:
name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
- untracked: true
+ paths:
+ - binaries/
```
---
@@ -994,7 +986,8 @@ If you use **Windows Batch** to run your shell scripts you need to replace
job:
artifacts:
name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%"
- untracked: true
+ paths:
+ - binaries/
```
If you use **Windows PowerShell** to run your shell scripts you need to replace
@@ -1004,7 +997,33 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace
job:
artifacts:
name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME"
- untracked: true
+ paths:
+ - binaries/
+```
+
+### `artifacts:untracked`
+
+`artifacts:untracked` is used to add all Git untracked files as artifacts (along
+to the paths defined in `artifacts:paths`).
+
+NOTE: **Note:**
+To exclude the folders/files which should not be a part of `untracked` just
+add them to `.gitignore`.
+
+Send all Git untracked files:
+
+```yaml
+artifacts:
+ untracked: true
+```
+
+Send all Git untracked files and files in `binaries`:
+
+```yaml
+artifacts:
+ untracked: true
+ paths:
+ - binaries/
```
### `artifacts:when`
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 856ef882453..b1bec84a2f3 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -131,6 +131,9 @@ There is also and alternative method to [translate messages from validation erro
### Interpolation
+Placeholders in translated text should match the code style of the respective source file.
+For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+
- In Ruby/HAML:
```ruby
@@ -141,11 +144,19 @@ There is also and alternative method to [translate messages from validation erro
```js
import { __, sprintf } from '~/locale';
- sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe'
+
+ sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
```
-The placeholders should match the code style of the respective source file.
-For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+ By default, `sprintf` escapes the placeholder values.
+ If you want to take care of that yourself, you can pass `false` as third argument.
+
+ ```js
+ import { __, sprintf } from '~/locale';
+
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
+ ```
### Plurals
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 0c63f51cb45..0a6f402d5d2 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -152,19 +152,33 @@ is sufficient (and saves you some time).
### Live testing and focused testing
While developing locally, it may be helpful to keep karma running so that you
-can get instant feedback on as you write tests and modify code. To do this
-you can start karma with `npm run karma-start`. It will compile the javascript
+can get instant feedback on as you write tests and modify code. To do this
+you can start karma with `yarn run karma-start`. It will compile the javascript
assets and run a server at `http://localhost:9876/` where it will automatically
-run the tests on any browser which connects to it. You can enter that url on
+run the tests on any browser which connects to it. You can enter that url on
multiple browsers at once to have it run the tests on each in parallel.
While karma is running, any changes you make will instantly trigger a recompile
and retest of the entire test suite, so you can see instantly if you've broken
-a test with your changes. You can use [jasmine focused][jasmine-focus] or
+a test with your changes. You can use [jasmine focused][jasmine-focus] or
excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the
tests you want while you're working on a specific feature, but make sure to
remove these directives when you commit your code.
+It is also possible to only run karma on specific folders or files by filtering
+the run tests via the argument `--filter-spec` or short `-f`:
+
+```bash
+# Run all files
+yarn karma-start
+# Run specific spec files
+yarn karma-start --filter-spec profile/account/components/update_username_spec.js
+# Run specific spec folder
+yarn karma-start --filter-spec profile/account/components/
+# Run all specs which path contain vue_shared or vie
+yarn karma-start -f vue_shared -f vue_mr_widget
+```
+
## RSpec feature integration tests
Information on setting up and running RSpec integration tests with
@@ -176,7 +190,7 @@ Information on setting up and running RSpec integration tests with
Similar errors will be thrown if you're using JavaScript features not yet
supported by the PhantomJS test runner which is used for both Karma and RSpec
-tests. We polyfill some JavaScript objects for older browsers, but some
+tests. We polyfill some JavaScript objects for older browsers, but some
features are still unavailable:
- Array.from
@@ -188,7 +202,7 @@ features are still unavailable:
- Symbol/Symbol.iterator
- Spread
-Until these are polyfilled appropriately, they should not be used. Please
+Until these are polyfilled appropriately, they should not be used. Please
update this list with additional unsupported features.
### RSpec errors due to JavaScript
@@ -223,7 +237,7 @@ end
### Spinach errors due to missing JavaScript
NOTE: **Note:** Since we are discouraging the use of Spinach when writing new
-feature tests, you shouldn't ever need to use this. This information is kept
+feature tests, you shouldn't ever need to use this. This information is kept
available for legacy purposes only.
In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 3cf6f7b7ddf..85174b64ff7 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -301,7 +301,7 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-6-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-7-stable gitlab
**Note:** You can change `10-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e88b787187c..89b8ea209b3 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -455,17 +455,19 @@ The following variables can be used for setting up the Auto DevOps domain,
providing a custom Helm chart, or scaling your application. PostgreSQL can be
also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
-| **Variable** | **Description** |
-| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
-| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). |
-| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment; defaults to 1. |
-| `CANARY_PRODUCTION_REPLICAS`| The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. |
-| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
-| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
-| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
-| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
-| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`|
+| **Variable** | **Description** |
+| ------------ | --------------- |
+| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
+| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). |
+| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
+| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
+| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
+| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
+| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
+| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
+| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
+| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. |
+| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
TIP: **Tip:**
Set up the replica variables using a
@@ -496,8 +498,9 @@ The general rule is: `TRACK_ENV_REPLICAS`. Where:
That way, you can define your own `TRACK_ENV_REPLICAS` variables with which
you will be able to scale the pod's replicas easily.
-In the example below, the environment's name is `qa` which would result in
-looking for the `QA_REPLICAS` environment variable:
+In the example below, the environment's name is `qa` and it deploys the track
+`foo` which would result in looking for the `FOO_QA_REPLICAS` environment
+variable:
```yaml
QA testing:
@@ -505,11 +508,11 @@ QA testing:
environment:
name: qa
script:
- - deploy qa
+ - deploy foo
```
-If, in addition, there was also a `track: foo` defined in the application's Helm
-chart, like:
+The track `foo` being referenced would also need to be defined in the
+application's Helm chart, like:
```yaml
replicaCount: 1
@@ -531,8 +534,6 @@ service:
internalPort: 5000
```
-then the environment variable would be `FOO_QA_REPLICAS`.
-
## Currently supported languages
NOTE: **Note:**
diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md
new file mode 100644
index 00000000000..4a76ae14d2e
--- /dev/null
+++ b/doc/update/10.6-to-10.7.md
@@ -0,0 +1,361 @@
+---
+comments: false
+---
+
+# From 10.6 to 10.7
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz
+echo '4e6a0f828819e15d274ae58485585fc8b7caace0 ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz
+cd ruby-2.3.6
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-7-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-6-stable:config/gitlab.yml.example origin/10-7-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/10-6-stable:lib/support/nginx/gitlab-ssl origin/10-7-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-6-stable:lib/support/nginx/gitlab origin/10-7-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-6-stable:lib/support/init.d/gitlab.default.example origin/10-7-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (10.5)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 10.5 to 10.6](10.5-to-10.6.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-7-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index eacfe2baa27..159109e8954 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -14,6 +14,10 @@ The comment area supports [Markdown] and [quick actions]. One can edit their
own comment at any time, and anyone with [Master access level][permissions] or
higher can also edit a comment made by someone else.
+You could also reply to the notification email in order to reply to a comment,
+provided that [Reply by email] is configured by your GitLab admin. This also
+supports [Markdown] and [quick actions] as if replied from the web.
+
Apart from the standard comments, you also have the option to create a comment
in the form of a resolvable or threaded discussion.
@@ -283,3 +287,4 @@ edit existing comments. Non-team members are restricted from adding or editing c
[markdown]: ../markdown.md
[quick actions]: ../project/quick_actions.md
[permissions]: ../permissions.md
+[Reply by email]: ../../administration/reply_by_email.md
diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md
index 34a0b97a171..bf6c0dc0e7e 100644
--- a/doc/user/project/integrations/prometheus_library/cloudwatch.md
+++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring AWS resources, sta
## Requirements
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
## Metrics supported
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
index 518018e5839..cd398f7c0fd 100644
--- a/doc/user/project/integrations/prometheus_library/haproxy.md
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -5,7 +5,7 @@ GitLab has support for automatically detecting and monitoring HAProxy. This is p
## Requirements
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
## Metrics supported
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index f09ecf9ff2d..96a22316265 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -1,4 +1,5 @@
# Prometheus Metrics library
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
@@ -15,7 +16,7 @@ We have tried to surface the most important metrics for each exporter, and will
GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment.
In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that,
-GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
+GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
## Adding to the library
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index 7fb8369d3c1..fea3231006b 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
## Requirements
-The [Prometheus service](../prometheus/index.md) must be enabled.
+The [Prometheus service](../prometheus.md) must be enabled.
## Metrics supported
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 49b34c82ae6..590b1c4275a 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -6,7 +6,7 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
## Requirements
-[Prometheus integration](../prometheus/index.md) must be active.
+[Prometheus integration](../prometheus.md) must be active.
## Metrics supported
@@ -27,7 +27,7 @@ For other deployments, there is [some configuration](#manually-setting-up-nginx-
### About managed NGINX Ingress deployments
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](https://docs.gitlab.com/ce/user/project/clusters/index.html#getting-the-external-ip-address).
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
NGINX is configured for Prometheus monitoring, by setting:
* `enable-vts-status: "true"`, to export Prometheus metrics
@@ -51,4 +51,4 @@ Managing these settings depends on how NGINX ingress has been deployed. If you h
In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
-If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
+If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
deleted file mode 100644
index c57376aecff..00000000000
--- a/features/project/commits/branches.feature
+++ /dev/null
@@ -1,42 +0,0 @@
-@project_commits
-Feature: Project Commits Branches
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has protected branches
-
- Scenario: I can see project all git branches
- Given I visit project branches page
- Then I should see "Shop" all branches list
-
- Scenario: I can see project protected git branches
- Given I visit project protected branches page
- Then I should see "Shop" protected branches list
-
- @javascript
- Scenario: I create a branch
- Given I visit project branches page
- And I click new branch link
- And I submit new branch form
- Then I should see new branch created
-
- @javascript
- Scenario: I delete a branch
- Given I visit project branches page
- And I filter for branch improve/awesome
- And I click branch 'improve/awesome' delete link
- Then I should not see branch 'improve/awesome'
-
- @javascript
- Scenario: I create a branch with invalid name
- Given I visit project branches page
- And I click new branch link
- And I submit new branch form with invalid name
- Then I should see new an error that branch is invalid
-
- @javascript
- Scenario: I create a branch that already exists
- Given I visit project branches page
- And I click new branch link
- And I submit new branch form with branch that already exists
- Then I should see new an error that branch already exists
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
deleted file mode 100644
index 77c8ed6e5bf..00000000000
--- a/features/project/issues/milestones.feature
+++ /dev/null
@@ -1,43 +0,0 @@
-@project_issues
-Feature: Project Issues Milestones
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has milestone "v2.2"
- Given I visit project "Shop" milestones page
-
- Scenario: I should see active milestones
- Then I should see milestone "v2.2"
-
- Scenario: I should see milestone
- Given I click link "v2.2"
- Then I should see milestone "v2.2"
-
- @javascript
- Scenario: I create and delete new milestone
- Given I click link "New Milestone"
- And I submit new milestone "v2.3"
- Then I should see milestone "v2.3"
- Given I click button to remove milestone
- And I confirm in modal
- When I visit project "Shop" activity page
- Then I should see deleted milestone activity
-
- @javascript
- Scenario: I delete new milestone
- Given I click button to remove milestone
- And I confirm in modal
- And I should see no milestones
-
- @javascript
- Scenario: Listing closed issues
- Given the milestone has open and closed issues
- And I click link "v2.2"
- Then I should see 3 issues
-
- # Markdown
-
- Scenario: Headers inside the description should have ids generated for them.
- Given I click link "v2.2"
- # PLEASE USE the `have_header_with_correct_id_and_link(level, text, id, parent)` matcher on migrating this spec to rspec.
- Then Header "Description header" should have correct id and link
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index c3ae33d2aa9..3ecd4c8b672 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -7,37 +7,14 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
click_link "All"
end
- step 'I should see "Shop" all branches list' do
- expect(page).to have_content "Branches"
- expect(page).to have_content "master"
- end
-
step 'I click link "Protected"' do
click_link "Protected"
end
- step 'I should see "Shop" protected branches list' do
- page.within ".protected-branches-list" do
- expect(page).to have_content "stable"
- expect(page).not_to have_content "master"
- end
- end
-
- step 'project "Shop" has protected branches' do
- project = Project.find_by(name: "Shop")
- create(:protected_branch, project: project, name: "stable")
- end
-
step 'I click new branch link' do
click_link "New branch"
end
- step 'I submit new branch form' do
- fill_in 'branch_name', with: 'deploy_keys'
- select_branch('master')
- click_button 'Create branch'
- end
-
step 'I submit new branch form with invalid name' do
fill_in 'branch_name', with: '1.0 stable'
page.find("body").click # defocus the branch_name input
@@ -45,40 +22,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
click_button 'Create branch'
end
- step 'I submit new branch form with branch that already exists' do
- fill_in 'branch_name', with: 'master'
- select_branch('master')
- click_button 'Create branch'
- end
-
- step 'I should see new branch created' do
- expect(page).to have_content 'deploy_keys'
- end
-
- step 'I should see new an error that branch is invalid' do
- expect(page).to have_content 'Branch name is invalid'
- expect(page).to have_content "can't contain spaces"
- end
-
- step 'I should see new an error that branch already exists' do
- expect(page).to have_content 'Branch already exists'
- end
-
- step 'I filter for branch improve/awesome' do
- fill_in 'branch-search', with: 'improve/awesome'
- find('#branch-search').native.send_keys(:enter)
- end
-
- step "I click branch 'improve/awesome' delete link" do
- page.within '.js-branch-improve\/awesome' do
- accept_alert { find('.btn-remove').click }
- end
- end
-
- step "I should not see branch 'improve/awesome'" do
- expect(page).to have_css('.js-branch-improve\\/awesome', visible: :hidden)
- end
-
def select_branch(branch_name)
find('.git-revision-dropdown-toggle').click
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 4ce67aa651c..30927306a4f 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -4,35 +4,6 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
include SharedPaths
include SharedMarkdown
- step 'I should see milestone "v2.2"' do
- milestone = @project.milestones.find_by(title: "v2.2")
- expect(page).to have_content(milestone.title[0..10])
- expect(page).to have_content(milestone.expires_at)
- expect(page).to have_content("Issues")
- end
-
- step 'I click link "v2.2"' do
- click_link "v2.2"
- end
-
- step 'I click link "New Milestone"' do
- page.within('.nav-controls') do
- click_link "New milestone"
- end
- end
-
- step 'I submit new milestone "v2.3"' do
- fill_in "milestone_title", with: "v2.3"
- click_button "Create milestone"
- end
-
- step 'I should see milestone "v2.3"' do
- milestone = @project.milestones.find_by(title: "v2.3")
- expect(page).to have_content(milestone.title[0..10])
- expect(page).to have_content(milestone.expires_at)
- expect(page).to have_content("Issues")
- end
-
step 'project "Shop" has milestone "v2.2"' do
project = Project.find_by(name: "Shop")
milestone = create(:milestone,
@@ -43,36 +14,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
3.times { create(:issue, project: project, milestone: milestone) }
end
- step 'the milestone has open and closed issues' do
- project = Project.find_by(name: "Shop")
- milestone = project.milestones.find_by(title: 'v2.2')
-
- # 3 Open issues created above; create one closed issue
- create(:closed_issue, project: project, milestone: milestone)
- end
-
- step 'I should see deleted milestone activity' do
- expect(page).to have_content('opened milestone in')
- expect(page).to have_content('destroyed milestone in')
- end
-
When 'I click link "All Issues"' do
click_link 'All Issues'
end
-
- step 'I should see 3 issues' do
- expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
- end
-
- step 'I click button to remove milestone' do
- click_button 'Delete'
- end
-
- step 'I confirm in modal' do
- click_button 'Delete milestone'
- end
-
- step 'I should see no milestones' do
- expect(page).to have_content('No milestones to show')
- end
end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index c66280127e9..9d522936fb6 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -10,10 +10,6 @@ module SharedMarkdown
expect(find(:xpath, "#{node.path}/..").text).to eq text
end
- step 'Header "Description header" should have correct id and link' do
- header_should_have_correct_id_and_link(1, 'Description header', 'description-header')
- end
-
step 'I should not see the Markdown preview' do
expect(find('.gfm-form .js-md-preview')).not_to be_visible
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index cc893b8391e..3b4c839bcef 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -264,10 +264,6 @@ module SharedPaths
visit project_path(project)
end
- step 'I visit project "Shop" activity page' do
- visit activity_project_path(project)
- end
-
step 'I visit project "Forked Shop" merge requests page' do
visit project_merge_requests_path(@forked_project)
end
@@ -276,14 +272,6 @@ module SharedPaths
visit edit_project_path(project)
end
- step 'I visit project branches page' do
- visit project_branches_path(@project)
- end
-
- step 'I visit project protected branches page' do
- visit project_protected_branches_path(@project)
- end
-
step 'I visit compare refs page' do
visit project_compare_index_path(@project)
end
@@ -381,10 +369,6 @@ module SharedPaths
visit project_merge_requests_path(project)
end
- step 'I visit project "Shop" milestones page' do
- visit project_milestones_path(project)
- end
-
step 'I visit project "Shop" team page' do
visit project_project_members_path(project)
end
diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb
index f5ff95e3eb3..c351a155ae5 100644
--- a/lib/banzai/commit_renderer.rb
+++ b/lib/banzai/commit_renderer.rb
@@ -3,7 +3,7 @@ module Banzai
ATTRIBUTES = [:description, :title].freeze
def self.render(commits, project, user = nil)
- obj_renderer = ObjectRenderer.new(project, user)
+ obj_renderer = ObjectRenderer.new(user: user, default_project: project)
ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) }
end
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
index 8f541dcfdb2..1a415232545 100644
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -11,7 +11,8 @@ module Banzai
def call
return doc unless context[:issuable_state_filter_enabled]
- extractor = Banzai::IssuableExtractor.new(project, current_user)
+ context = RenderContext.new(project, current_user)
+ extractor = Banzai::IssuableExtractor.new(context)
issuables = extractor.extract([doc])
issuables.each do |node, issuable|
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index 9f9882b3b40..caf11fe94c4 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,7 +7,11 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- Redactor.new(project, current_user).redact([doc]) unless context[:skip_redaction]
+ unless context[:skip_redaction]
+ context = RenderContext.new(project, current_user)
+
+ Redactor.new(context).redact([doc])
+ end
doc
end
diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb
index 49603d0b363..ae7dc71e7eb 100644
--- a/lib/banzai/issuable_extractor.rb
+++ b/lib/banzai/issuable_extractor.rb
@@ -12,11 +12,11 @@ module Banzai
[@data-reference-type="issue" or @data-reference-type="merge_request"]
).freeze
- attr_reader :project, :user
+ attr_reader :context
- def initialize(project, user)
- @project = project
- @user = user
+ # context - An instance of Banzai::RenderContext.
+ def initialize(context)
+ @context = context
end
# Returns Hash in the form { node => issuable_instance }
@@ -25,8 +25,10 @@ module Banzai
document.xpath(QUERY)
end
- issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user)
- merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
+ issue_parser = Banzai::ReferenceParser::IssueParser.new(context)
+
+ merge_request_parser =
+ Banzai::ReferenceParser::MergeRequestParser.new(context)
issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge(
merge_request_parser.records_for_nodes(nodes)
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 2691be81623..a176f1e261b 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -13,14 +13,13 @@ module Banzai
# As an example, rendering the attribute `note` would place the unredacted
# HTML into `note_html` and the redacted HTML into `redacted_note_html`.
class ObjectRenderer
- attr_reader :project, :user
+ attr_reader :context
- # project - A Project to use for redacting Markdown.
+ # default_project - A default Project to use for redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any.
# redaction_context - A Hash containing extra attributes to use during redaction
- def initialize(project, user = nil, redaction_context = {})
- @project = project
- @user = user
+ def initialize(default_project: nil, user: nil, redaction_context: {})
+ @context = RenderContext.new(default_project, user)
@redaction_context = base_context.merge(redaction_context)
end
@@ -48,17 +47,21 @@ module Banzai
pipeline = HTML::Pipeline.new([])
objects.map do |object|
- pipeline.to_document(Banzai.render_field(object, attribute))
+ document = pipeline.to_document(Banzai.render_field(object, attribute))
+
+ context.associate_document(document, object)
+
+ document
end
end
def post_process_documents(documents, objects, attribute)
# Called here to populate cache, refer to IssuableExtractor docs
- IssuableExtractor.new(project, user).extract(documents)
+ IssuableExtractor.new(context).extract(documents)
documents.zip(objects).map do |document, object|
- context = context_for(object, attribute)
- Banzai::Pipeline[:post_process].to_document(document, context)
+ pipeline_context = context_for(document, object, attribute)
+ Banzai::Pipeline[:post_process].to_document(document, pipeline_context)
end
end
@@ -66,20 +69,21 @@ module Banzai
#
# Returns an Array containing the redacted documents.
def redact_documents(documents)
- redactor = Redactor.new(project, user)
+ redactor = Redactor.new(context)
redactor.redact(documents)
end
# Returns a Banzai context for the given object and attribute.
- def context_for(object, attribute)
- @redaction_context.merge(object.banzai_render_context(attribute))
+ def context_for(document, object, attribute)
+ @redaction_context.merge(object.banzai_render_context(attribute)).merge(
+ project: context.project_for_node(document)
+ )
end
def base_context
{
- current_user: user,
- project: project,
+ current_user: context.current_user,
skip_redaction: true
}
end
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
index fd457bebf03..28928d6f376 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/redactor.rb
@@ -2,13 +2,15 @@ module Banzai
# Class for removing Markdown references a certain user is not allowed to
# view.
class Redactor
- attr_reader :user, :project
+ attr_reader :context
- # project - A Project to use for redacting links.
- # user - The currently logged in user (if any).
- def initialize(project, user = nil)
- @project = project
- @user = user
+ # context - An instance of `Banzai::RenderContext`.
+ def initialize(context)
+ @context = context
+ end
+
+ def user
+ context.current_user
end
# Redacts the references in the given Array of documents.
@@ -70,11 +72,11 @@ module Banzai
end
def redact_cross_project_references(documents)
- extractor = Banzai::IssuableExtractor.new(project, user)
+ extractor = Banzai::IssuableExtractor.new(context)
issuables = extractor.extract(documents)
issuables.each do |node, issuable|
- next if issuable.project == project
+ next if issuable.project == context.project_for_node(node)
node['class'] = node['class'].gsub('has-tooltip', '')
node['title'] = nil
@@ -95,7 +97,7 @@ module Banzai
end
per_type.each do |type, nodes|
- parser = Banzai::ReferenceParser[type].new(project, user)
+ parser = Banzai::ReferenceParser[type].new(context)
visible.merge(parser.nodes_visible_to_user(user, nodes))
end
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index 7e6357f8a00..78588299c18 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -10,8 +10,8 @@ module Banzai
end
def references(type, project, current_user = nil)
- processor = Banzai::ReferenceParser[type]
- .new(project, current_user)
+ context = RenderContext.new(project, current_user)
+ processor = Banzai::ReferenceParser[type].new(context)
processor.process(html_documents)
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 279fca8d043..68752f5bb5a 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -45,9 +45,13 @@ module Banzai
@data_attribute ||= "data-#{reference_type.to_s.dasherize}"
end
- def initialize(project = nil, current_user = nil)
- @project = project
- @current_user = current_user
+ # context - An instance of `Banzai::RenderContext`.
+ def initialize(context)
+ @context = context
+ end
+
+ def project_for_node(node)
+ context.project_for_node(node)
end
# Returns all the nodes containing references that the user can refer to.
@@ -224,7 +228,11 @@ module Banzai
private
- attr_reader :current_user, :project
+ attr_reader :context
+
+ def current_user
+ context.current_user
+ end
# When a feature is disabled or visible only for
# team members we should not allow team members
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 230827129b6..6bee5ea15b9 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -5,15 +5,10 @@ module Banzai
def nodes_visible_to_user(user, nodes)
issues = records_for_nodes(nodes)
- issues_to_check = issues.values
+ issues_to_check, cross_project_issues = partition_issues(issues, user)
- unless can?(user, :read_cross_project)
- issues_to_check, cross_project_issues = issues_to_check.partition do |issue|
- issue.project == project
- end
- end
-
- readable_issues = Ability.issues_readable_by_user(issues_to_check, user).to_set
+ readable_issues =
+ Ability.issues_readable_by_user(issues_to_check, user).to_set
nodes.select do |node|
issue_in_node = issues[node]
@@ -25,7 +20,7 @@ module Banzai
# but not the issue.
if readable_issues.include?(issue_in_node)
true
- elsif cross_project_issues&.include?(issue_in_node)
+ elsif cross_project_issues.include?(issue_in_node)
can_read_reference?(user, issue_in_node)
else
false
@@ -33,6 +28,32 @@ module Banzai
end
end
+ # issues - A Hash mapping HTML nodes to their corresponding Issue
+ # instances.
+ # user - The current User.
+ def partition_issues(issues, user)
+ return [issues.values, []] if can?(user, :read_cross_project)
+
+ issues_to_check = []
+ cross_project_issues = []
+
+ # We manually partition the data since our input is a Hash and our
+ # output has to be an Array of issues; not an Array of (node, issue)
+ # pairs.
+ issues.each do |node, issue|
+ target =
+ if issue.project == project_for_node(node)
+ issues_to_check
+ else
+ cross_project_issues
+ end
+
+ target << issue
+ end
+
+ [issues_to_check, cross_project_issues]
+ end
+
def records_for_nodes(nodes)
@issues_for_nodes ||= grouped_objects_for_nodes(
nodes,
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index 8932d4f2905..ceb7f1d165c 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -58,7 +58,7 @@ module Banzai
def can_read_project_reference?(node)
node_id = node.attr('data-project').to_i
- project && project.id == node_id
+ project_for_node(node)&.id == node_id
end
def nodes_user_can_reference(current_user, nodes)
@@ -71,6 +71,7 @@ module Banzai
nodes.select do |node|
project_id = node.attr(project_attr)
user_id = node.attr(author_attr)
+ project = project_for_node(node)
if project && project_id && project.id == project_id.to_i
true
diff --git a/lib/banzai/render_context.rb b/lib/banzai/render_context.rb
new file mode 100644
index 00000000000..e30fc9f469b
--- /dev/null
+++ b/lib/banzai/render_context.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Banzai
+ # Object storing the current user, project, and other details used when
+ # parsing Markdown references.
+ class RenderContext
+ attr_reader :current_user
+
+ # default_project - The default project to use for all documents, if any.
+ # current_user - The user viewing the document, if any.
+ def initialize(default_project = nil, current_user = nil)
+ @current_user = current_user
+ @projects = Hash.new(default_project)
+ end
+
+ # Associates an HTML document with a Project.
+ #
+ # document - The HTML document to map to a Project.
+ # object - The object that produced the HTML document.
+ def associate_document(document, object)
+ # XML nodes respond to "document" but will return a Document instance,
+ # even when they belong to a DocumentFragment.
+ document = document.document if document.fragment?
+
+ @projects[document] = object.project if object.respond_to?(:project)
+ end
+
+ def project_for_node(node)
+ @projects[node.document]
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
index c0c7c7f5b5d..c1fc70ac266 100644
--- a/lib/gitlab/ci/status/build/common.rb
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -3,6 +3,14 @@ module Gitlab
module Status
module Build
module Common
+ def illustration
+ {
+ image: 'illustrations/skipped-job_empty.svg',
+ size: 'svg-430',
+ title: _('This job does not have a trace.')
+ }
+ end
+
def has_details?
can?(user, :read_build, subject)
end
diff --git a/lib/gitlab/ci/status/build/erased.rb b/lib/gitlab/ci/status/build/erased.rb
index 3a5113b16b6..495227c2ffb 100644
--- a/lib/gitlab/ci/status/build/erased.rb
+++ b/lib/gitlab/ci/status/build/erased.rb
@@ -5,7 +5,7 @@ module Gitlab
class Erased < Status::Extended
def illustration
{
- image: 'illustrations/skipped-job_empty.svg',
+ image: 'illustrations/erased-log_empty.svg',
size: 'svg-430',
title: _('Job has been erased')
}
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
index 23ed71db8b0..d00e5b07f95 100644
--- a/lib/gitlab/ci/variables/collection/item.rb
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -3,12 +3,9 @@ module Gitlab
module Variables
class Collection
class Item
- def initialize(**options)
+ def initialize(key:, value:, public: true, file: false)
@variable = {
- key: options.fetch(:key),
- value: options.fetch(:value),
- public: options.fetch(:public, true),
- file: options.fetch(:files, false)
+ key: key, value: value, public: public, file: file
}
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index d4e893b881c..c9abea90d21 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -1,5 +1,9 @@
module Gitlab
module Git
+ # The ID of empty tree.
+ # See http://stackoverflow.com/a/40884093/1856239 and
+ # https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
+ EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
BLANK_SHA = ('0' * 40).freeze
TAG_REF_PREFIX = "refs/tags/".freeze
BRANCH_REF_PREFIX = "refs/heads/".freeze
diff --git a/lib/gitlab/git/attributes_parser.rb b/lib/gitlab/git/attributes_parser.rb
index d8aeabb6cba..08f4d7d4f5c 100644
--- a/lib/gitlab/git/attributes_parser.rb
+++ b/lib/gitlab/git/attributes_parser.rb
@@ -3,12 +3,8 @@ module Gitlab
# Class for parsing Git attribute files and extracting the attributes for
# file patterns.
class AttributesParser
- def initialize(attributes_data)
+ def initialize(attributes_data = "")
@data = attributes_data || ""
-
- if @data.is_a?(File)
- @patterns = parse_file
- end
end
# Returns all the Git attributes for the given path.
@@ -28,7 +24,7 @@ module Gitlab
# Returns a Hash containing the file patterns and their attributes.
def patterns
- @patterns ||= parse_file
+ @patterns ||= parse_data
end
# Parses an attribute string.
@@ -91,8 +87,8 @@ module Gitlab
private
- # Parses the Git attributes file.
- def parse_file
+ # Parses the Git attributes file contents.
+ def parse_data
pairs = []
comment = '#'
diff --git a/lib/gitlab/git/info_attributes.rb b/lib/gitlab/git/info_attributes.rb
deleted file mode 100644
index e79a440950b..00000000000
--- a/lib/gitlab/git/info_attributes.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# Gitaly note: JV: not sure what to make of this class. Why does it use
-# the full disk path of the repository to look up attributes This is
-# problematic in Gitaly, because Gitaly hides the full disk path to the
-# repository from gitlab-ce.
-
-module Gitlab
- module Git
- # Parses gitattributes at `$GIT_DIR/info/attributes`
- #
- # Unlike Rugged this parser only needs a single IO call (a call to `open`),
- # vastly reducing the time spent in extracting attributes.
- #
- # This class _only_ supports parsing the attributes file located at
- # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
- # (`.gitattributes` is copied to this particular path).
- #
- # Basic usage:
- #
- # attributes = Gitlab::Git::InfoAttributes.new(some_repo.path)
- #
- # attributes.attributes('README.md') # => { "eol" => "lf }
- class InfoAttributes
- delegate :attributes, :patterns, to: :parser
-
- # path - The path to the Git repository.
- def initialize(path)
- @repo_path = File.expand_path(path)
- end
-
- def parser
- @parser ||= begin
- if File.exist?(attributes_path)
- File.open(attributes_path, 'r') do |file_handle|
- AttributesParser.new(file_handle)
- end
- else
- AttributesParser.new("")
- end
- end
- end
-
- private
-
- def attributes_path
- @attributes_path ||= File.join(@repo_path, 'info/attributes')
- end
- end
- end
-end
diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb
new file mode 100644
index 00000000000..eb3d8819239
--- /dev/null
+++ b/lib/gitlab/git/raw_diff_change.rb
@@ -0,0 +1,60 @@
+module Gitlab
+ module Git
+ # This class behaves like a struct with fields :blob_id, :blob_size, :operation, :old_path, :new_path
+ # All those fields are (binary) strings or integers
+ class RawDiffChange
+ attr_reader :blob_id, :blob_size, :old_path, :new_path, :operation
+
+ def initialize(raw_change)
+ parse(raw_change)
+ end
+
+ private
+
+ # Input data has the following format:
+ #
+ # When a file has been modified:
+ # 7e3e39ebb9b2bf433b4ad17313770fbe4051649c 669 M\tfiles/ruby/popen.rb
+ #
+ # When a file has been renamed:
+ # 85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee
+ def parse(raw_change)
+ @blob_id, @blob_size, @raw_operation, raw_paths = raw_change.split(' ', 4)
+ @operation = extract_operation
+ @old_path, @new_path = extract_paths(raw_paths)
+ end
+
+ def extract_paths(file_path)
+ case operation
+ when :renamed
+ file_path.split(/\t/)
+ when :deleted
+ [file_path, nil]
+ when :added
+ [nil, file_path]
+ else
+ [file_path, file_path]
+ end
+ end
+
+ def extract_operation
+ case @raw_operation&.first(1)
+ when 'A'
+ :added
+ when 'C'
+ :copied
+ when 'D'
+ :deleted
+ when 'M'
+ :modified
+ when 'R'
+ :renamed
+ when 'T'
+ :type_changed
+ else
+ :unknown
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index f1b575bd872..36992cbcca0 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -105,7 +105,6 @@ module Gitlab
)
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
- @attributes = Gitlab::Git::InfoAttributes.new(path)
end
def ==(other)
@@ -559,6 +558,24 @@ module Gitlab
count_commits(from: from, to: to, **options)
end
+ # old_rev and new_rev are commit ID's
+ # the result of this method is an array of Gitlab::Git::RawDiffChange
+ def raw_changes_between(old_rev, new_rev)
+ result = []
+
+ circuit_breaker.perform do
+ Open3.pipeline_r(git_diff_cmd(old_rev, new_rev), format_git_cat_file_script, git_cat_file_cmd) do |last_stdout, wait_threads|
+ last_stdout.each_line { |line| result << ::Gitlab::Git::RawDiffChange.new(line.chomp!) }
+
+ if wait_threads.any? { |waiter| !waiter.value&.success? }
+ raise ::Gitlab::Git::Repository::GitError, "Unabled to obtain changes between #{old_rev} and #{new_rev}"
+ end
+ end
+ end
+
+ result
+ end
+
# Returns the SHA of the most recent common ancestor of +from+ and +to+
def merge_base(from, to)
gitaly_migrate(:merge_base) do |is_enabled|
@@ -993,11 +1010,32 @@ module Gitlab
raise InvalidRef
end
+ def info_attributes
+ return @info_attributes if @info_attributes
+
+ content =
+ gitaly_migrate(:get_info_attributes) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.info_attributes
+ else
+ attributes_path = File.join(File.expand_path(@path), 'info', 'attributes')
+
+ if File.exist?(attributes_path)
+ File.read(attributes_path)
+ else
+ ""
+ end
+ end
+ end
+
+ @info_attributes = AttributesParser.new(content)
+ end
+
# Returns the Git attributes for the given file path.
#
# See `Gitlab::Git::Attributes` for more information.
def attributes(path)
- @attributes.attributes(path)
+ info_attributes.attributes(path)
end
def gitattribute(path, name)
@@ -1385,7 +1423,8 @@ module Gitlab
end
def branch_names_contains_sha(sha)
- gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
+ gitaly_migrate(:branch_names_contains_sha,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_ref_client.branch_names_contains_sha(sha)
else
@@ -1395,7 +1434,8 @@ module Gitlab
end
def tag_names_contains_sha(sha)
- gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
+ gitaly_migrate(:tag_names_contains_sha,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_ref_client.tag_names_contains_sha(sha)
else
@@ -2461,6 +2501,35 @@ module Gitlab
result.to_s(16)
end
+
+ def build_git_cmd(*args)
+ object_directories = alternate_object_directories.join(File::PATH_SEPARATOR)
+
+ env = { 'PWD' => self.path }
+ env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = object_directories if object_directories.present?
+
+ [
+ env,
+ ::Gitlab.config.git.bin_path,
+ *args,
+ { chdir: self.path }
+ ]
+ end
+
+ def git_diff_cmd(old_rev, new_rev)
+ old_rev = old_rev == ::Gitlab::Git::BLANK_SHA ? ::Gitlab::Git::EMPTY_TREE_ID : old_rev
+
+ build_git_cmd('diff', old_rev, new_rev, '--raw')
+ end
+
+ def git_cat_file_cmd
+ format = '%(objectname) %(objectsize) %(rest)'
+ build_git_cmd('cat-file', "--batch-check=#{format}")
+ end
+
+ def format_git_cat_file_script
+ File.expand_path('../support/format-git-cat-file-input', __FILE__)
+ end
end
end
end
diff --git a/lib/gitlab/git/support/format-git-cat-file-input b/lib/gitlab/git/support/format-git-cat-file-input
new file mode 100755
index 00000000000..2e93c646d0f
--- /dev/null
+++ b/lib/gitlab/git/support/format-git-cat-file-input
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+# This script formats the output of the `git diff <old_rev> <new_rev> --raw`
+# command so it can be processed by `git cat-file`
+
+# We need to convert this:
+# ":100644 100644 5f53439... 85bc2f9... R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee"
+# To:
+# "85bc2f9 R\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee"
+
+ARGF.each do |line|
+ _, _, old_blob_id, new_blob_id, rest = line.split(/\s/, 5)
+
+ old_blob_id.gsub!(/[^\h]/, '')
+ new_blob_id.gsub!(/[^\h]/, '')
+
+ # We can't pass '0000000...' to `git cat-file` given it will not return info about the deleted file
+ blob_id = new_blob_id =~ /\A0+\z/ ? old_blob_id : new_blob_id
+
+ $stdout.puts "#{blob_id} #{rest}"
+end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 456a8a1a2d6..a36e6c822f9 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -3,10 +3,6 @@ module Gitlab
class CommitService
include Gitlab::EncodingHelper
- # The ID of empty tree.
- # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
- EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
-
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@repository = repository
@@ -37,7 +33,7 @@ module Gitlab
def diff(from, to, options = {})
from_id = case from
when NilClass
- EMPTY_TREE_ID
+ Gitlab::Git::EMPTY_TREE_ID
else
if from.respond_to?(:oid)
# This is meant to match a Rugged::Commit. This should be impossible in
@@ -50,7 +46,7 @@ module Gitlab
to_id = case to
when NilClass
- EMPTY_TREE_ID
+ Gitlab::Git::EMPTY_TREE_ID
else
if to.respond_to?(:oid)
# This is meant to match a Rugged::Commit. This should be impossible in
@@ -352,7 +348,7 @@ module Gitlab
end
def diff_from_parent_request_params(commit, options = {})
- parent_id = commit.parent_ids.first || EMPTY_TREE_ID
+ parent_id = commit.parent_ids.first || Gitlab::Git::EMPTY_TREE_ID
diff_between_commits_request_params(parent_id, commit.id, options)
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 6441065f5fe..39057beefba 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -50,6 +50,15 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end
+ def info_attributes
+ request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo)
+
+ response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request)
+ response.each_with_object("") do |message, attributes|
+ attributes << message.attributes
+ end
+ end
+
def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true)
request = Gitaly::FetchRemoteRequest.new(
repository: @gitaly_repo, remote: remote, force: forced,
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 0d8dd5cb8f4..7a698e4b3f3 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -136,7 +136,7 @@ module Gitlab
wiki_file = nil
response.each do |message|
- next unless message.name.present?
+ next unless message.name.present? || wiki_file
if wiki_file
wiki_file.raw_data << message.raw_data
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index b0a492eaa58..aeda66763e8 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -73,6 +73,10 @@ module Gitlab
nil
end
+ def bytes_to_megabytes(bytes)
+ bytes.to_f / Numeric::MEGABYTE
+ end
+
# Used in EE
# Accepts either an Array or a String and returns an array
def ensure_array_from_string(string_or_array)
diff --git a/lib/rspec_flaky/config.rb b/lib/rspec_flaky/config.rb
index a17ae55910e..06e96f969f1 100644
--- a/lib/rspec_flaky/config.rb
+++ b/lib/rspec_flaky/config.rb
@@ -1,9 +1,7 @@
-require 'json'
-
module RspecFlaky
class Config
def self.generate_report?
- ENV['FLAKY_RSPEC_GENERATE_REPORT'] == 'true'
+ !!(ENV['FLAKY_RSPEC_GENERATE_REPORT'] =~ /1|true/)
end
def self.suite_flaky_examples_report_path
diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb
index 973c95b0212..dea23c325be 100644
--- a/lib/rspec_flaky/flaky_examples_collection.rb
+++ b/lib/rspec_flaky/flaky_examples_collection.rb
@@ -1,11 +1,9 @@
-require 'json'
+require 'active_support/hash_with_indifferent_access'
+
+require_relative 'flaky_example'
module RspecFlaky
class FlakyExamplesCollection < SimpleDelegator
- def self.from_json(json)
- new(JSON.parse(json))
- end
-
def initialize(collection = {})
unless collection.is_a?(Hash)
raise ArgumentError, "`collection` must be a Hash, #{collection.class} given!"
@@ -22,7 +20,7 @@ module RspecFlaky
super(Hash[collection_of_flaky_examples])
end
- def to_report
+ def to_h
Hash[map { |uid, example| [uid, example.to_h] }].deep_symbolize_keys
end
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
index 4a5bfec9967..5b5e4f7c7de 100644
--- a/lib/rspec_flaky/listener.rb
+++ b/lib/rspec_flaky/listener.rb
@@ -1,5 +1,11 @@
require 'json'
+require_relative 'config'
+require_relative 'example'
+require_relative 'flaky_example'
+require_relative 'flaky_examples_collection'
+require_relative 'report'
+
module RspecFlaky
class Listener
# - suite_flaky_examples: contains all the currently tracked flacky example
@@ -9,7 +15,7 @@ module RspecFlaky
attr_reader :suite_flaky_examples, :flaky_examples
def initialize(suite_flaky_examples_json = nil)
- @flaky_examples = FlakyExamplesCollection.new
+ @flaky_examples = RspecFlaky::FlakyExamplesCollection.new
@suite_flaky_examples = init_suite_flaky_examples(suite_flaky_examples_json)
end
@@ -18,47 +24,36 @@ module RspecFlaky
return unless current_example.attempts > 1
- flaky_example = suite_flaky_examples.fetch(current_example.uid) { FlakyExample.new(current_example) }
+ flaky_example = suite_flaky_examples.fetch(current_example.uid) { RspecFlaky::FlakyExample.new(current_example) }
flaky_example.update_flakiness!(last_attempts_count: current_example.attempts)
flaky_examples[current_example.uid] = flaky_example
end
def dump_summary(_)
- write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
+ RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path)
+ # write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
new_flaky_examples = flaky_examples - suite_flaky_examples
if new_flaky_examples.any?
Rails.logger.warn "\nNew flaky examples detected:\n"
- Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_report)
+ Rails.logger.warn JSON.pretty_generate(new_flaky_examples.to_h)
- write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
+ RspecFlaky::Report.new(new_flaky_examples).write(RspecFlaky::Config.new_flaky_examples_report_path)
+ # write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
end
end
- def to_report(examples)
- Hash[examples.map { |k, ex| [k, ex.to_h] }]
- end
-
private
def init_suite_flaky_examples(suite_flaky_examples_json = nil)
- unless suite_flaky_examples_json
+ if suite_flaky_examples_json
+ RspecFlaky::Report.load_json(suite_flaky_examples_json).flaky_examples
+ else
return {} unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path)
- suite_flaky_examples_json = File.read(RspecFlaky::Config.suite_flaky_examples_report_path)
+ RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).flaky_examples
end
-
- FlakyExamplesCollection.from_json(suite_flaky_examples_json)
- end
-
- def write_report_file(examples_collection, file_path)
- return unless RspecFlaky::Config.generate_report?
-
- report_path_dir = File.dirname(file_path)
- FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir)
-
- File.write(file_path, JSON.pretty_generate(examples_collection.to_report))
end
end
end
diff --git a/lib/rspec_flaky/report.rb b/lib/rspec_flaky/report.rb
new file mode 100644
index 00000000000..a8730d3b7c7
--- /dev/null
+++ b/lib/rspec_flaky/report.rb
@@ -0,0 +1,54 @@
+require 'json'
+require 'time'
+
+require_relative 'config'
+require_relative 'flaky_examples_collection'
+
+module RspecFlaky
+ # This class is responsible for loading/saving JSON reports, and pruning
+ # outdated examples.
+ class Report < SimpleDelegator
+ OUTDATED_DAYS_THRESHOLD = 90
+
+ attr_reader :flaky_examples
+
+ def self.load(file_path)
+ load_json(File.read(file_path))
+ end
+
+ def self.load_json(json)
+ new(RspecFlaky::FlakyExamplesCollection.new(JSON.parse(json)))
+ end
+
+ def initialize(flaky_examples)
+ unless flaky_examples.is_a?(RspecFlaky::FlakyExamplesCollection)
+ raise ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, #{flaky_examples.class} given!"
+ end
+
+ @flaky_examples = flaky_examples
+ super(flaky_examples)
+ end
+
+ def write(file_path)
+ unless RspecFlaky::Config.generate_report?
+ puts "! Generating reports is disabled. To enable it, please set the `FLAKY_RSPEC_GENERATE_REPORT=1` !" # rubocop:disable Rails/Output
+ return
+ end
+
+ report_path_dir = File.dirname(file_path)
+ FileUtils.mkdir_p(report_path_dir) unless Dir.exist?(report_path_dir)
+
+ File.write(file_path, JSON.pretty_generate(flaky_examples.to_h))
+ end
+
+ def prune_outdated(days: OUTDATED_DAYS_THRESHOLD)
+ outdated_date_threshold = Time.now - (3600 * 24 * days)
+ updated_hash = flaky_examples.dup
+ .delete_if do |uid, hash|
+ hash[:last_flaky_at] && Time.parse(hash[:last_flaky_at]).to_i < outdated_date_threshold.to_i
+ end
+
+ self.class.new(RspecFlaky::FlakyExamplesCollection.new(updated_hash))
+ end
+ end
+end
diff --git a/package.json b/package.json
index 27f612aca38..7bac672a0b2 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"eslint": "eslint --max-warnings 0 --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
- "karma": "karma start config/karma.config.js --single-run",
- "karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
+ "karma": "karma start --single-run true config/karma.config.js",
+ "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "karma start config/karma.config.js",
"prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
@@ -98,6 +98,7 @@
"axios-mock-adapter": "^1.10.0",
"babel-eslint": "^8.0.2",
"babel-plugin-istanbul": "^4.1.5",
+ "commander": "^2.15.1",
"eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-import-resolver-webpack": "^0.8.3",
diff --git a/scripts/prune-old-flaky-specs b/scripts/prune-old-flaky-specs
new file mode 100755
index 00000000000..f7451fbd428
--- /dev/null
+++ b/scripts/prune-old-flaky-specs
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+# lib/rspec_flaky/flaky_examples_collection.rb is requiring
+# `active_support/hash_with_indifferent_access`, and we install the `activesupport`
+# gem manually on the CI
+require 'rubygems'
+
+require_relative '../lib/rspec_flaky/report'
+
+report_file = ARGV.shift
+unless report_file
+ puts 'usage: prune-old-flaky-specs <report-file> <new-report-file>'
+ exit 1
+end
+
+new_report_file = ARGV.shift || report_file
+report = RspecFlaky::Report.load(report_file)
+puts "Loading #{report_file}..."
+puts "Current report has #{report.size} entries."
+
+new_report = report.prune_outdated
+
+puts "New report has #{new_report.size} entries: #{report.size - new_report.size} entries older than 90 days were removed."
+puts "Saved #{new_report_file}." if new_report.write(new_report_file)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 986f864f0b5..257a3822503 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -89,7 +89,7 @@ feature 'Dashboard Projects' do
end
describe 'with a pipeline', :clean_gitlab_redis_shared_state do
- let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
+ let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) }
before do
# Since the cache isn't updated when a new pipeline is created
@@ -102,7 +102,7 @@ feature 'Dashboard Projects' do
visit dashboard_projects_path
page.within('.controls') do
- expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
+ expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
expect(page).to have_link('Commit: passed')
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
deleted file mode 100644
index 50c5e0bb65f..00000000000
--- a/spec/features/milestones/show_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'rails_helper'
-
-describe 'Milestone show' do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:milestone) { create(:milestone, project: project) }
- let(:labels) { create_list(:label, 2, project: project) }
- let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } }
-
- before do
- project.add_user(user, :developer)
- sign_in(user)
- end
-
- def visit_milestone
- visit project_milestone_path(project, milestone)
- end
-
- it 'avoids N+1 database queries' do
- create(:labeled_issue, issue_params)
- control = ActiveRecord::QueryRecorder.new { visit_milestone }
- create_list(:labeled_issue, 10, issue_params)
-
- expect { visit_milestone }.not_to exceed_query_limit(control)
- end
-end
diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb
new file mode 100644
index 00000000000..8fd057d587c
--- /dev/null
+++ b/spec/features/milestones/user_creates_milestone_spec.rb
@@ -0,0 +1,29 @@
+require "rails_helper"
+
+describe "User creates milestone", :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ visit(new_project_milestone_path(project))
+ end
+
+ it "creates milestone" do
+ TITLE = "v2.3".freeze
+
+ fill_in("Title", with: TITLE)
+ fill_in("Description", with: "# Description header")
+ click_button("Create milestone")
+
+ expect(page).to have_content(TITLE)
+ .and have_content("Issues")
+ .and have_header_with_correct_id_and_link(1, "Description header", "description-header")
+
+ visit(activity_project_path(project))
+
+ expect(page).to have_content("#{user.name} opened milestone")
+ end
+end
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
new file mode 100644
index 00000000000..414702daba4
--- /dev/null
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -0,0 +1,25 @@
+require "rails_helper"
+
+describe "User deletes milestone", :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:milestone) { create(:milestone, project: project) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ visit(project_milestones_path(project))
+ end
+
+ it "deletes milestone" do
+ click_button("Delete")
+ click_button("Delete milestone")
+
+ expect(page).to have_content("No milestones to show")
+
+ visit(activity_project_path(project))
+
+ expect(page).to have_content("#{user.name} destroyed milestone")
+ end
+end
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
new file mode 100644
index 00000000000..83d8e2ff9e9
--- /dev/null
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -0,0 +1,31 @@
+require "rails_helper"
+
+describe "User views milestone" do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:milestone) { create(:milestone, project: project) }
+ set(:labels) { create_list(:label, 2, project: project) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it "avoids N+1 database queries" do
+ ISSUE_PARAMS = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze
+
+ create(:labeled_issue, ISSUE_PARAMS)
+
+ control = ActiveRecord::QueryRecorder.new { visit_milestone }
+
+ create(:labeled_issue, ISSUE_PARAMS)
+
+ expect { visit_milestone }.not_to exceed_query_limit(control)
+ end
+
+ private
+
+ def visit_milestone
+ visit(project_milestone_path(project, milestone))
+ end
+end
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
new file mode 100644
index 00000000000..bebe40f73fd
--- /dev/null
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -0,0 +1,35 @@
+require "rails_helper"
+
+describe "User views milestones" do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:milestone) { create(:milestone, project: project) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ visit(project_milestones_path(project))
+ end
+
+ it "shows milestone" do
+ expect(page).to have_content(milestone.title)
+ .and have_content(milestone.expires_at)
+ .and have_content("Issues")
+ end
+
+ context "with issues" do
+ set(:issue) { create(:issue, project: project, milestone: milestone) }
+ set(:closed_issue) { create(:closed_issue, project: project, milestone: milestone) }
+
+ it "opens milestone" do
+ click_link(milestone.title)
+
+ expect(current_path).to eq(project_milestone_path(project, milestone))
+ expect(page).to have_content(milestone.title)
+ .and have_selector("#tab-issues li.issuable-row", count: 2)
+ .and have_content(issue.title)
+ .and have_content(closed_issue.title)
+ end
+ end
+end
diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb
new file mode 100644
index 00000000000..b706ad64954
--- /dev/null
+++ b/spec/features/projects/branches/user_creates_branch_spec.rb
@@ -0,0 +1,46 @@
+require "spec_helper"
+
+describe "User creates branch", :js do
+ include Spec::Support::Helpers::Features::BranchesHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ visit(new_project_branch_path(project))
+ end
+
+ it "creates new branch" do
+ BRANCH_NAME = "deploy_keys".freeze
+
+ create_branch(BRANCH_NAME)
+
+ expect(page).to have_content(BRANCH_NAME)
+ end
+
+ context "when branch name is invalid" do
+ it "does not create new branch" do
+ INVALID_BRANCH_NAME = "1.0 stable".freeze
+
+ fill_in("branch_name", with: INVALID_BRANCH_NAME)
+ page.find("body").click # defocus the branch_name input
+
+ select_branch("master")
+ click_button("Create branch")
+
+ expect(page).to have_content("Branch name is invalid")
+ expect(page).to have_content("can't contain spaces")
+ end
+ end
+
+ context "when branch name already exists" do
+ it "does not create new branch" do
+ create_branch("master")
+
+ expect(page).to have_content("Branch already exists")
+ end
+ end
+end
diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb
new file mode 100644
index 00000000000..96f215e1606
--- /dev/null
+++ b/spec/features/projects/branches/user_deletes_branch_spec.rb
@@ -0,0 +1,23 @@
+require "spec_helper"
+
+describe "User deletes branch", :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :repository) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ visit(project_branches_path(project))
+ end
+
+ it "deletes branch" do
+ fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter)
+
+ page.within(".js-branch-improve\\/awesome") do
+ accept_alert { find(".btn-remove").click }
+ end
+
+ expect(page).to have_css(".js-branch-improve\\/awesome", visible: :hidden)
+ end
+end
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
new file mode 100644
index 00000000000..62ae793151c
--- /dev/null
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -0,0 +1,34 @@
+require "spec_helper"
+
+describe "User views branches" do
+ set(:project) { create(:project, :repository) }
+ set(:user) { project.owner }
+
+ before do
+ sign_in(user)
+ end
+
+ context "all branches" do
+ before do
+ visit(project_branches_path(project))
+ end
+
+ it "shows branches" do
+ expect(page).to have_content("Branches").and have_content("master")
+ end
+ end
+
+ context "protected branches" do
+ set(:protected_branch) { create(:protected_branch, project: project) }
+
+ before do
+ visit(project_protected_branches_path(project))
+ end
+
+ it "shows branches" do
+ page.within(".protected-branches-list") do
+ expect(page).to have_content(protected_branch.name).and have_no_content("master")
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
new file mode 100644
index 00000000000..2fb9da2f0a2
--- /dev/null
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'user reads pipeline status', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:v110_pipeline) { create_pipeline('v1.1.0', 'success') }
+ let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') }
+
+ before do
+ project.add_master(user)
+
+ project.repository.add_tag(user, 'x1.1.0', 'v1.1.0')
+ v110_pipeline
+ x110_pipeline
+
+ sign_in(user)
+ end
+
+ shared_examples 'visiting project tree' do
+ scenario 'sees the correct pipeline status' do
+ visit project_tree_path(project, expected_pipeline.ref)
+ wait_for_requests
+
+ page.within('.blob-commit-info') do
+ expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline))
+ expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}")
+ end
+ end
+ end
+
+ it_behaves_like 'visiting project tree' do
+ let(:expected_pipeline) { v110_pipeline }
+ end
+
+ it_behaves_like 'visiting project tree' do
+ let(:expected_pipeline) { x110_pipeline }
+ end
+
+ def create_pipeline(ref, status)
+ create(:ci_pipeline,
+ project: project,
+ ref: ref,
+ sha: project.commit(ref).sha,
+ status: status)
+ end
+end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 749a1b81872..a460024542c 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -464,6 +464,17 @@ feature 'Jobs' do
expect(page).to have_content('This job has been skipped')
end
end
+
+ context 'when job is failed but has no trace' do
+ let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+
+ it 'renders empty state' do
+ visit project_job_path(project, job)
+
+ expect(job).not_to have_trace
+ expect(page).to have_content('This job does not have a trace.')
+ end
+ end
end
describe "POST /:project/jobs/:id/cancel", :js do
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index bc75dc5d19b..9e10bfb2adc 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -392,7 +392,7 @@ feature 'Login' do
end
def ensure_one_active_tab
- expect(page).to have_selector('.nav-tabs > li.active', count: 1)
+ expect(page).to have_selector('ul.new-session-tabs > li.active', count: 1)
end
def ensure_one_active_pane
diff --git a/spec/fixtures/big-image.png b/spec/fixtures/big-image.png
new file mode 100644
index 00000000000..a333363ac36
--- /dev/null
+++ b/spec/fixtures/big-image.png
Binary files differ
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index ce96e90e2d7..46c55da24f8 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -322,74 +322,6 @@ describe ProjectsHelper do
end
end
- describe "#project_feature_access_select" do
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
-
- context "when project is internal or public" do
- it "shows all options" do
- helper.instance_variable_set(:@project, project)
- result = helper.project_feature_access_select(:issues_access_level)
- expect(result).to include("Disabled")
- expect(result).to include("Only team members")
- expect(result).to include("Everyone with access")
- end
- end
-
- context "when project is private" do
- before do
- project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- end
-
- it "shows only allowed options" do
- helper.instance_variable_set(:@project, project)
- result = helper.project_feature_access_select(:issues_access_level)
- expect(result).to include("Disabled")
- expect(result).to include("Only team members")
- expect(result).to have_selector('option[disabled]', text: "Everyone with access")
- end
- end
-
- context "when project moves from public to private" do
- before do
- project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- end
-
- it "shows the highest allowed level selected" do
- helper.instance_variable_set(:@project, project)
- result = helper.project_feature_access_select(:issues_access_level)
-
- expect(result).to include("Disabled")
- expect(result).to include("Only team members")
- expect(result).to have_selector('option[disabled]', text: "Everyone with access")
- expect(result).to have_selector('option[selected]', text: "Only team members")
- end
- end
- end
-
- describe "#visibility_select_options" do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user) }
-
- before do
- allow(helper).to receive(:current_user).and_return(user)
-
- stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
- end
-
- it "does not include the Public restricted level" do
- expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).not_to include('Public')
- end
-
- it "includes the Internal level" do
- expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Internal')
- end
-
- it "includes the Private level" do
- expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
- end
- end
-
describe '#get_project_nav_tabs' do
let(:project) { create(:project) }
let(:user) { create(:user) }
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
index 93c0cf97ff0..c38fe8b1f25 100644
--- a/spec/javascripts/fixtures/linked_tabs.html.haml
+++ b/spec/javascripts/fixtures/linked_tabs.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-tabs.linked-tabs
+%ul.nav-links.new-session-tabs.linked-tabs
%li
%a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
Tab 1
diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml
index 12b8d423cbe..2e00fe7865e 100644
--- a/spec/javascripts/fixtures/signin_tabs.html.haml
+++ b/spec/javascripts/fixtures/signin_tabs.html.haml
@@ -1,5 +1,5 @@
-%ul.nav-tabs
+%ul.nav-links.new-session-tabs
+ %li.active
+ %a{ href: '#ldap' } LDAP
%li
- %a.active{ id: 'standard', href: '#standard'} Standard
- %li
- %a{ id: 'ldap', href: '#ldap'} Ldap
+ %a{ href: '#login-pane'} Standard
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 63a3d2c6cd5..310d222377f 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -1,9 +1,12 @@
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
import monacoLoader from '~/ide/monaco_loader';
import Editor from '~/ide/lib/editor';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
+import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
describe('RepoEditor', () => {
@@ -35,7 +38,7 @@ describe('RepoEditor', () => {
resetStore(vm.$store);
- Editor.editorInstance.modelManager.dispose();
+ Editor.editorInstance.dispose();
});
it('renders an ide container', done => {
@@ -79,16 +82,30 @@ describe('RepoEditor', () => {
});
describe('when file is markdown and viewer mode is review', () => {
+ let mock;
+
beforeEach(done => {
+ mock = new MockAdapter(axios);
+
+ vm.file.projectId = 'namespace/project';
vm.file.previewMode = {
id: 'markdown',
previewTitle: 'Preview Markdown',
};
+ vm.file.content = 'testing 123';
vm.$store.state.viewer = 'diff';
+ mock.onPost(/(.*)\/preview_markdown/).reply(200, {
+ body: '<p>testing 123</p>',
+ });
+
vm.$nextTick(done);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('renders an Edit and a Preview Tab', done => {
Vue.nextTick(() => {
const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li');
@@ -99,6 +116,26 @@ describe('RepoEditor', () => {
done();
});
});
+
+ it('renders markdown for tempFile', done => {
+ vm.file.tempFile = true;
+ vm.file.path = `${vm.file.path}.md`;
+ vm.$store.state.entries[vm.file.path] = vm.file;
+
+ vm
+ .$nextTick()
+ .then(() => {
+ vm.$el.querySelectorAll('.ide-mode-tabs .nav-links a')[1].click();
+ })
+ .then(setTimeoutPromise)
+ .then(() => {
+ expect(vm.$el.querySelector('.preview-container').innerHTML).toContain(
+ '<p>testing 123</p>',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
describe('when open file is binary and not raw', () => {
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
index e58a8018ed5..61ee2dc13ca 100644
--- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -1,42 +1,36 @@
-import _ from 'underscore';
-import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import PipelineMediator from '~/pipelines/pipeline_details_mediator';
describe('PipelineMdediator', () => {
let mediator;
+ let mock;
+
beforeEach(() => {
- mediator = new PipelineMediator({ endpoint: 'foo' });
+ mock = new MockAdapter(axios);
+ mediator = new PipelineMediator({ endpoint: 'foo.json' });
+ });
+
+ afterEach(() => {
+ mock.restore();
});
it('should set defaults', () => {
- expect(mediator.options).toEqual({ endpoint: 'foo' });
+ expect(mediator.options).toEqual({ endpoint: 'foo.json' });
expect(mediator.state.isLoading).toEqual(false);
expect(mediator.store).toBeDefined();
expect(mediator.service).toBeDefined();
});
describe('request and store data', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({ foo: 'bar' }), {
- status: 200,
- }));
- };
-
- beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
- });
-
- it('should store received data', (done) => {
+ it('should store received data', done => {
+ mock.onGet('foo.json').reply(200, { id: '121123' });
mediator.fetchPipeline();
setTimeout(() => {
- expect(mediator.store.state.pipeline).toEqual({ foo: 'bar' });
+ expect(mediator.store.state.pipeline).toEqual({ id: '121123' });
done();
- });
+ }, 0);
});
});
});
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 61c2f783acc..c2ed2e9a31b 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -1,27 +1,35 @@
-import _ from 'underscore';
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import stage from '~/pipelines/components/stage.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines stage component', () => {
let StageComponent;
let component;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
StageComponent = Vue.extend(stage);
- component = new StageComponent({
- propsData: {
- stage: {
- status: {
- group: 'success',
- icon: 'icon_status_success',
- title: 'success',
- },
- dropdown_path: 'foo',
+ component = mountComponent(StageComponent, {
+ stage: {
+ status: {
+ group: 'success',
+ icon: 'icon_status_success',
+ title: 'success',
},
- updateDropdown: false,
+ dropdown_path: 'path.json',
},
- }).$mount();
+ updateDropdown: false,
+ });
+ });
+
+ afterEach(() => {
+ component.$destroy();
+ mock.restore();
});
it('should render a dropdown with the status icon', () => {
@@ -31,23 +39,11 @@ describe('Pipelines stage component', () => {
});
describe('with successfull request', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({ html: 'foo' }), {
- status: 200,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, interceptor,
- );
+ mock.onGet('path.json').reply(200, { html: 'foo' });
});
- it('should render the received data', (done) => {
+ it('should render the received data', done => {
component.$el.querySelector('button').click();
setTimeout(() => {
@@ -60,20 +56,8 @@ describe('Pipelines stage component', () => {
});
describe('when request fails', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({}), {
- status: 500,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, interceptor,
- );
+ mock.onGet('path.json').reply(500);
});
it('should close the dropdown', () => {
@@ -86,33 +70,18 @@ describe('Pipelines stage component', () => {
});
describe('update endpoint correctly', () => {
- const updatedInterceptor = (request, next) => {
- if (request.url === 'bar') {
- next(request.respondWith(JSON.stringify({ html: 'this is the updated content' }), {
- status: 200,
- }));
- }
- next();
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(updatedInterceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, updatedInterceptor,
- );
+ mock.onGet('bar.json').reply(200, { html: 'this is the updated content' });
});
- it('should update the stage to request the new endpoint provided', (done) => {
+ it('should update the stage to request the new endpoint provided', done => {
component.stage = {
status: {
group: 'running',
icon: 'running',
title: 'running',
},
- dropdown_path: 'bar',
+ dropdown_path: 'bar.json',
};
Vue.nextTick(() => {
@@ -121,7 +90,7 @@ describe('Pipelines stage component', () => {
setTimeout(() => {
expect(
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
- ).toEqual('this is the updated content');
+ ).toEqual('this is the updated content');
done();
});
});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index b1b03ef1e09..423432c9e5d 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -4,7 +4,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
(() => {
describe('SigninTabsMemoizer', () => {
const fixtureTemplate = 'static/signin_tabs.html.raw';
- const tabSelector = 'ul.nav-tabs';
+ const tabSelector = 'ul.new-session-tabs';
const currentTabKey = 'current_signin_tab';
let memo;
@@ -27,7 +27,7 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
it('does nothing if no tab was previously selected', () => {
createMemoizer();
- expect(document.querySelector('li a.active').getAttribute('id')).toEqual('standard');
+ expect(document.querySelector(`${tabSelector} > li.active a`).getAttribute('href')).toEqual('#ldap');
});
it('shows last selected tab on boot', () => {
@@ -48,9 +48,9 @@ import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
it('saves last selected tab on change', () => {
createMemoizer();
- document.getElementById('standard').click();
+ document.querySelector('a[href="#login-pane"]').click();
- expect(memo.readData()).toEqual('#standard');
+ expect(memo.readData()).toEqual('#login-pane');
});
it('overrides last selected tab with hash tag when given', () => {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index d158786e484..14bff05e537 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -5,6 +5,7 @@ import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
+import Translate from '~/vue_shared/translate';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
@@ -22,12 +23,13 @@ Vue.config.warnHandler = (msg, vm, trace) => {
};
let hasVueErrors = false;
-Vue.config.errorHandler = function (err) {
+Vue.config.errorHandler = function(err) {
hasVueErrors = true;
fail(err);
};
Vue.use(VueResource);
+Vue.use(Translate);
// enable test fixtures
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
@@ -43,10 +45,11 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
+gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
-window.addEventListener('unhandledrejection', (event) => {
+window.addEventListener('unhandledrejection', event => {
hasUnhandledPromiseRejections = true;
console.error('Unhandled promise rejection:');
console.error(event.reason.stack || event.reason);
@@ -69,15 +72,25 @@ beforeEach(() => {
const axiosDefaultAdapter = getDefaultAdapter();
+let testFiles = process.env.TEST_FILES || [];
+if (testFiles.length > 0) {
+ testFiles = testFiles.map(path => path.replace(/^spec\/javascripts\//, '').replace(/\.js$/, ''));
+ console.log(`Running only tests matching: ${testFiles}`);
+} else {
+ console.log('Running all tests');
+}
+
// render all of our tests
const testsContext = require.context('.', true, /_spec$/);
-testsContext.keys().forEach(function (path) {
+testsContext.keys().forEach(function(path) {
try {
- testsContext(path);
+ if (testFiles.length === 0 || testFiles.some(p => path.includes(p))) {
+ testsContext(path);
+ }
} catch (err) {
console.error('[ERROR] Unable to load spec: ', path);
- describe('Test bundle', function () {
- it(`includes '${path}'`, function () {
+ describe('Test bundle', function() {
+ it(`includes '${path}'`, function() {
expect(err).toBeNull();
});
});
@@ -85,7 +98,7 @@ testsContext.keys().forEach(function (path) {
});
describe('test errors', () => {
- beforeAll((done) => {
+ beforeAll(done => {
if (hasUnhandledPromiseRejections || hasVueWarnings || hasVueErrors) {
setTimeout(done, 1000);
} else {
@@ -149,18 +162,18 @@ if (process.env.BABEL_ENV === 'coverage') {
'./issue_show/index.js',
];
- describe('Uncovered files', function () {
+ describe('Uncovered files', function() {
const sourceFiles = require.context('~', true, /\.js$/);
$.holdReady(true);
- sourceFiles.keys().forEach(function (path) {
+ sourceFiles.keys().forEach(function(path) {
// ignore if there is a matching spec file
if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) {
return;
}
- it(`includes '${path}'`, function () {
+ it(`includes '${path}'`, function() {
try {
sourceFiles(path);
} catch (err) {
diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js
deleted file mode 100644
index 82714cb69bd..00000000000
--- a/spec/javascripts/visibility_select_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import VisibilitySelect from '~/visibility_select';
-
-(() => {
- describe('VisibilitySelect', function () {
- const lockedElement = document.createElement('div');
- lockedElement.dataset.helpBlock = 'lockedHelpBlock';
-
- const checkedElement = document.createElement('div');
- checkedElement.dataset.description = 'checkedDescription';
-
- const mockElements = {
- container: document.createElement('div'),
- select: document.createElement('div'),
- '.help-block': document.createElement('div'),
- '.js-locked': lockedElement,
- 'option:checked': checkedElement,
- };
-
- beforeEach(function () {
- spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]);
- });
-
- describe('constructor', function () {
- beforeEach(function () {
- this.visibilitySelect = new VisibilitySelect(mockElements.container);
- });
-
- it('sets the container member', function () {
- expect(this.visibilitySelect.container).toEqual(mockElements.container);
- });
-
- it('queries and sets the helpBlock member', function () {
- expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block');
- expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']);
- });
-
- it('queries and sets the select member', function () {
- expect(Element.prototype.querySelector).toHaveBeenCalledWith('select');
- expect(this.visibilitySelect.select).toEqual(mockElements.select);
- });
-
- describe('if there is no container element provided', function () {
- it('throws an error', function () {
- expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1');
- });
- });
- });
-
- describe('init', function () {
- describe('if there is a select', function () {
- beforeEach(function () {
- this.visibilitySelect = new VisibilitySelect(mockElements.container);
- });
-
- it('calls updateHelpText', function () {
- spyOn(VisibilitySelect.prototype, 'updateHelpText');
- this.visibilitySelect.init();
- expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled();
- });
-
- it('adds a change event listener', function () {
- spyOn(this.visibilitySelect.select, 'addEventListener');
- this.visibilitySelect.init();
- expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change');
- });
- });
-
- describe('if there is no select', function () {
- beforeEach(function () {
- mockElements.select = undefined;
- this.visibilitySelect = new VisibilitySelect(mockElements.container);
- this.visibilitySelect.init();
- });
-
- it('updates the helpBlock text to the locked `data-help-block` messaged', function () {
- expect(this.visibilitySelect.helpBlock.textContent)
- .toEqual(lockedElement.dataset.helpBlock);
- });
-
- afterEach(function () {
- mockElements.select = document.createElement('div');
- });
- });
- });
-
- describe('updateHelpText', function () {
- beforeEach(function () {
- this.visibilitySelect = new VisibilitySelect(mockElements.container);
- this.visibilitySelect.init();
- });
-
- it('updates the helpBlock text to the selected options `data-description`', function () {
- expect(this.visibilitySelect.helpBlock.textContent)
- .toEqual(checkedElement.dataset.description);
- });
- });
- });
-})();
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index 046968fbc1f..d797f1266df 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -1,47 +1,37 @@
import Vue from 'vue';
import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('UnresolvedDiscussions', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = UnresolvedDiscussions.props;
+ const Component = Vue.extend(UnresolvedDiscussions);
+ let vm;
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ afterEach(() => {
+ vm.$destroy();
});
- describe('template', () => {
- let el;
- let vm;
- const path = 'foo/bar';
-
+ describe('with discussions path', () => {
beforeEach(() => {
- const Component = Vue.extend(UnresolvedDiscussions);
- const mr = {
- createIssueToResolveDiscussionsPath: path,
- };
- vm = new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
- el = vm.$el;
+ vm = mountComponent(Component, { mr: {
+ createIssueToResolveDiscussionsPath: gl.TEST_HOST,
+ } });
});
it('should have correct elements', () => {
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
- expect(el.innerText).toContain('Create an issue to resolve them later');
- expect(el.querySelector('.js-create-issue').getAttribute('href')).toEqual(path);
+ expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
+ expect(vm.$el.innerText).toContain('Create an issue to resolve them later');
+ expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(gl.TEST_HOST);
});
+ });
- it('should not show create issue button if user cannot create issue', (done) => {
- vm.mr.createIssueToResolveDiscussionsPath = '';
+ describe('without discussions path', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {} });
+ });
- Vue.nextTick(() => {
- expect(el.querySelector('.js-create-issue')).toEqual(null);
- done();
- });
+ it('should not show create issue link if user cannot create issue', () => {
+ expect(vm.$el.innerText).toContain('There are unresolved discussions. Please resolve these discussions');
+ expect(vm.$el.querySelector('.js-create-issue')).toEqual(null);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
index edebd822295..02117638b63 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -1,10 +1,11 @@
import Vue from 'vue';
+import $ from 'jquery';
import headerComponent from '~/vue_shared/components/markdown/header.vue';
describe('Markdown field header component', () => {
let vm;
- beforeEach((done) => {
+ beforeEach(done => {
const Component = Vue.extend(headerComponent);
vm = new Component({
@@ -17,24 +18,18 @@ describe('Markdown field header component', () => {
});
it('renders markdown buttons', () => {
- expect(
- vm.$el.querySelectorAll('.js-md').length,
- ).toBe(7);
+ expect(vm.$el.querySelectorAll('.js-md').length).toBe(7);
});
it('renders `write` link as active when previewMarkdown is false', () => {
- expect(
- vm.$el.querySelector('li:nth-child(1)').classList.contains('active'),
- ).toBeTruthy();
+ expect(vm.$el.querySelector('li:nth-child(1)').classList.contains('active')).toBeTruthy();
});
- it('renders `preview` link as active when previewMarkdown is true', (done) => {
+ it('renders `preview` link as active when previewMarkdown is true', done => {
vm.previewMarkdown = true;
Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('li:nth-child(2)').classList.contains('active'),
- ).toBeTruthy();
+ expect(vm.$el.querySelector('li:nth-child(2)').classList.contains('active')).toBeTruthy();
done();
});
@@ -52,16 +47,24 @@ describe('Markdown field header component', () => {
expect(vm.$emit).toHaveBeenCalledWith('write-markdown');
});
- it('blurs preview link after click', (done) => {
+ it('does not emit toggle markdown event when triggered from another form', () => {
+ spyOn(vm, '$emit');
+
+ $(document).triggerHandler('markdown-preview:show', [
+ $('<form><textarea class="markdown-area"></textarea></textarea></form>'),
+ ]);
+
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+
+ it('blurs preview link after click', done => {
const link = vm.$el.querySelector('li:nth-child(2) a');
spyOn(HTMLElement.prototype, 'blur');
link.click();
setTimeout(() => {
- expect(
- link.blur,
- ).toHaveBeenCalled();
+ expect(link.blur).toHaveBeenCalled();
done();
});
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index e7ebb2a332f..1f53657c59c 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -6,7 +6,10 @@ describe Banzai::CommitRenderer do
user = build(:user)
project = create(:project, :repository)
- expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original
+ expect(Banzai::ObjectRenderer)
+ .to receive(:new)
+ .with(user: user, default_project: project)
+ .and_call_original
described_class::ATTRIBUTES.each do |attr|
expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index 69763476dac..f42951d9781 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::IssuableExtractor do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:extractor) { described_class.new(project, user) }
+ let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:issue_link) do
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 074d521a5c6..1fe034ae9a2 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -3,7 +3,14 @@ require 'spec_helper'
describe Banzai::ObjectRenderer do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
- let(:renderer) { described_class.new(project, user, custom_value: 'value') }
+ let(:renderer) do
+ described_class.new(
+ default_project: project,
+ user: user,
+ redaction_context: { custom_value: 'value' }
+ )
+ end
+
let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
describe '#render' do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index 441f3725985..aaeec953e4b 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::Redactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
- let(:redactor) { described_class.new(project, user) }
+ let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
describe '#redact' do
context 'when reference not visible to user' do
@@ -54,7 +54,7 @@ describe Banzai::Redactor do
context 'when project is in pending delete' do
let!(:issue) { create(:issue, project: project) }
- let(:redactor) { described_class.new(project, user) }
+ let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
before do
project.update(pending_delete: true)
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 6175d4c4ca9..4e6e8eca38a 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -5,13 +5,14 @@ describe Banzai::ReferenceParser::BaseParser do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
+ let(:context) { Banzai::RenderContext.new(project, user) }
subject do
klass = Class.new(described_class) do
self.reference_type = :foo
end
- klass.new(project, user)
+ klass.new(context)
end
describe '.reference_type=' do
@@ -23,6 +24,19 @@ describe Banzai::ReferenceParser::BaseParser do
end
end
+ describe '#project_for_node' do
+ it 'returns the Project for a node' do
+ document = instance_double('document', fragment?: false)
+ project = instance_double('project')
+ object = instance_double('object', project: project)
+ node = instance_double('node', document: document)
+
+ context.associate_document(document, object)
+
+ expect(subject.project_for_node(node)).to eq(project)
+ end
+ end
+
describe '#nodes_visible_to_user' do
let(:link) { empty_html_link }
@@ -164,7 +178,7 @@ describe Banzai::ReferenceParser::BaseParser do
self.reference_type = :test
end
- instance = dummy.new(project, user)
+ instance = dummy.new(Banzai::RenderContext.new(project, user))
document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>')
expect(instance).to receive(:gather_references)
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 3505659c2c3..cca53a8b9b9 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 21813177deb..23e16fe0213 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 25969b65168..1cb31e57114 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index cb7f8b20dda..77c2064caba 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -7,7 +7,7 @@ describe Banzai::ReferenceParser::IssueParser do
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:link) { empty_html_link }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
describe '#nodes_visible_to_user' do
context 'when the link has a data-issue attribute' do
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index b700161d6c2..e4df2533821 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::LabelParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:label) { create(:label, project: project) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 14542342cf6..5417b1f00be 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 7dacdf8d629..751d042ffde 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MilestoneParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 69ec3f66aa8..d410bd4c164 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -9,7 +9,7 @@ describe Banzai::ReferenceParser::SnippetParser do
let(:external_user) { create(:user, :external) }
let(:project_member) { create(:user) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
def visible_references(snippet_visibility, user = nil)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index b079a3be029..112447f098e 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::UserParser do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group, creator: user) }
- subject { described_class.new(project, user) }
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
describe '#referenced_by' do
diff --git a/spec/lib/banzai/render_context_spec.rb b/spec/lib/banzai/render_context_spec.rb
new file mode 100644
index 00000000000..ad17db11613
--- /dev/null
+++ b/spec/lib/banzai/render_context_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::RenderContext do
+ let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') }
+
+ describe '#project_for_node' do
+ it 'returns the default project if no associated project was found' do
+ project = instance_double('project')
+ context = described_class.new(project)
+
+ expect(context.project_for_node(document)).to eq(project)
+ end
+
+ it 'returns the associated project if one was associated explicitly' do
+ project = instance_double('project')
+ obj = instance_double('object', project: project)
+ context = described_class.new
+
+ context.associate_document(document, obj)
+
+ expect(context.project_for_node(document)).to eq(project)
+ end
+
+ it 'returns the project associated with a DocumentFragment when using a node' do
+ project = instance_double('project')
+ obj = instance_double('object', project: project)
+ context = described_class.new
+ node = document.children.first
+
+ context.associate_document(document, obj)
+
+ expect(context.project_for_node(node)).to eq(project)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 2cce7a23ea7..ca3c66f0152 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -38,4 +38,10 @@ describe Gitlab::Ci::Status::Build::Common do
expect(subject.details_path).to include "jobs/#{build.id}"
end
end
+
+ describe '#illustration' do
+ it 'provides a fallback empty state illustration' do
+ expect(subject.illustration).not_to be_empty
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 6d5b73bb01b..d53a7d468e3 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -75,7 +75,8 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Retryable, Gitlab::Ci::Status::Build::Failed]
+ .to eq [Gitlab::Ci::Status::Build::Retryable,
+ Gitlab::Ci::Status::Build::Failed]
end
it 'fabricates a failed build status' do
@@ -94,7 +95,7 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when build is allowed to fail' do
- let(:build) { create(:ci_build, :failed, :allowed_to_fail) }
+ let(:build) { create(:ci_build, :failed, :allowed_to_fail, :trace_artifact) }
it 'matches correct core status' do
expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index bf9208f1ff4..e79f0a7f257 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -5,6 +5,18 @@ describe Gitlab::Ci::Variables::Collection::Item do
{ key: 'VAR', value: 'something', public: true }
end
+ describe '.new' do
+ it 'raises error if unknown key i specified' do
+ expect { described_class.new(key: 'VAR', value: 'abc', files: true) }
+ .to raise_error ArgumentError, 'unknown keyword: files'
+ end
+
+ it 'raises error when required keywords are not specified' do
+ expect { described_class.new(key: 'VAR') }
+ .to raise_error ArgumentError, 'missing keyword: value'
+ end
+ end
+
describe '.fabricate' do
it 'supports using a hash' do
resource = described_class.fabricate(variable)
@@ -47,12 +59,25 @@ describe Gitlab::Ci::Variables::Collection::Item do
end
describe '#to_runner_variable' do
- it 'returns a runner-compatible hash representation' do
- runner_variable = described_class
- .new(**variable)
- .to_runner_variable
+ context 'when variable is not a file-related' do
+ it 'returns a runner-compatible hash representation' do
+ runner_variable = described_class
+ .new(**variable)
+ .to_runner_variable
+
+ expect(runner_variable).to eq variable
+ end
+ end
+
+ context 'when variable is file-related' do
+ it 'appends file description component' do
+ runner_variable = described_class
+ .new(key: 'VAR', value: 'value', file: true)
+ .to_runner_variable
- expect(runner_variable).to eq variable
+ expect(runner_variable)
+ .to eq(key: 'VAR', value: 'value', public: true, file: true)
+ end
end
end
end
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index 386d73e6115..cedbfcc0d18 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -25,12 +25,12 @@ describe Gitlab::Email::Handler do
described_class.for('email', address).class
end
- expect(matched_handlers.uniq).to match_array(Gitlab::Email::Handler::HANDLERS)
+ expect(matched_handlers.uniq).to match_array(ce_handlers)
end
it 'can pick exactly one handler for each address' do
addresses.each do |address|
- matched_handlers = Gitlab::Email::Handler::HANDLERS.select do |handler|
+ matched_handlers = ce_handlers.select do |handler|
handler.new('email', address).can_handle?
end
@@ -38,4 +38,10 @@ describe Gitlab::Email::Handler do
end
end
end
+
+ def ce_handlers
+ @ce_handlers ||= Gitlab::Email::Handler::HANDLERS.reject do |handler|
+ handler.name.start_with?('Gitlab::Email::Handler::EE::')
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 323334e99a5..2d103123998 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -66,18 +66,6 @@ describe Gitlab::Git::AttributesParser, seed_helper: true do
end
end
- context 'when attributes data is a file handle' do
- subject do
- File.open(attributes_path, 'r') do |file_handle|
- described_class.new(file_handle)
- end
- end
-
- it 'returns the attributes as a Hash' do
- expect(subject.attributes('test.txt')).to eq({ 'text' => true })
- end
- end
-
context 'when attributes data is nil' do
let(:data) { nil }
diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb
deleted file mode 100644
index ea84909c3e0..00000000000
--- a/spec/lib/gitlab/git/info_attributes_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::InfoAttributes, seed_helper: true do
- let(:path) do
- File.join(SEED_STORAGE_PATH, 'with-git-attributes.git')
- end
-
- subject { described_class.new(path) }
-
- describe '#attributes' do
- context 'using a path with attributes' do
- it 'returns the attributes as a Hash' do
- expect(subject.attributes('test.txt')).to eq({ 'text' => true })
- end
-
- it 'returns an empty Hash for a defined path without attributes' do
- expect(subject.attributes('bla/bla.txt')).to eq({})
- end
- end
- end
-
- describe '#parser' do
- it 'parses a file with entries' do
- expect(subject.patterns).to be_an_instance_of(Hash)
- expect(subject.patterns["/*.txt"]).to eq({ 'text' => true })
- end
-
- it 'does not parse anything when the attributes file does not exist' do
- expect(File).to receive(:exist?)
- .with(File.join(path, 'info/attributes'))
- .and_return(false)
-
- expect(subject.patterns).to eq({})
- end
-
- it 'does not parse attributes files with unsupported encoding' do
- path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git')
- subject = described_class.new(path)
-
- expect(subject.patterns).to eq({})
- end
- end
-end
diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb
new file mode 100644
index 00000000000..eedde34534f
--- /dev/null
+++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Git::RawDiffChange do
+ let(:raw_change) { }
+ let(:change) { described_class.new(raw_change) }
+
+ context 'bad input' do
+ let(:raw_change) { 'foo' }
+
+ it 'does not set most of the attrs' do
+ expect(change.blob_id).to eq('foo')
+ expect(change.operation).to eq(:unknown)
+ expect(change.old_path).to be_blank
+ expect(change.new_path).to be_blank
+ expect(change.blob_size).to be_blank
+ end
+ end
+
+ context 'adding a file' do
+ let(:raw_change) { '93e123ac8a3e6a0b600953d7598af629dec7b735 59 A bar/branch-test.txt' }
+
+ it 'initialize the proper attrs' do
+ expect(change.operation).to eq(:added)
+ expect(change.old_path).to be_blank
+ expect(change.new_path).to eq('bar/branch-test.txt')
+ expect(change.blob_id).to be_present
+ expect(change.blob_size).to be_present
+ end
+ end
+
+ context 'renaming a file' do
+ let(:raw_change) { "85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee" }
+
+ it 'initialize the proper attrs' do
+ expect(change.operation).to eq(:renamed)
+ expect(change.old_path).to eq('files/js/commit.js.coffee')
+ expect(change.new_path).to eq('files/js/commit.coffee')
+ expect(change.blob_id).to be_present
+ expect(change.blob_size).to be_present
+ end
+ end
+
+ context 'modifying a file' do
+ let(:raw_change) { 'c60514b6d3d6bf4bec1030f70026e34dfbd69ad5 824 M README.md' }
+
+ it 'initialize the proper attrs' do
+ expect(change.operation).to eq(:modified)
+ expect(change.old_path).to eq('README.md')
+ expect(change.new_path).to eq('README.md')
+ expect(change.blob_id).to be_present
+ expect(change.blob_size).to be_present
+ end
+ end
+
+ context 'deleting a file' do
+ let(:raw_change) { '60d7a906c2fd9e4509aeb1187b98d0ea7ce827c9 15364 D files/.DS_Store' }
+
+ it 'initialize the proper attrs' do
+ expect(change.operation).to eq(:deleted)
+ expect(change.old_path).to eq('files/.DS_Store')
+ expect(change.new_path).to be_nil
+ expect(change.blob_id).to be_present
+ expect(change.blob_size).to be_present
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index d3ab61746f4..1e00e8d2739 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1043,6 +1043,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to eq(17) }
end
+ describe '#raw_changes_between' do
+ let(:old_rev) { }
+ let(:new_rev) { }
+ let(:changes) { repository.raw_changes_between(old_rev, new_rev) }
+
+ context 'initial commit' do
+ let(:old_rev) { Gitlab::Git::BLANK_SHA }
+ let(:new_rev) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+
+ it 'returns the changes' do
+ expect(changes).to be_present
+ expect(changes.size).to eq(3)
+ end
+ end
+
+ context 'with an invalid rev' do
+ let(:old_rev) { 'foo' }
+ let(:new_rev) { 'bar' }
+
+ it 'returns an error' do
+ expect { changes }.to raise_error(Gitlab::Git::Repository::GitError)
+ end
+ end
+
+ context 'with valid revs' do
+ let(:old_rev) { 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6' }
+ let(:new_rev) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+ it 'returns the changes' do
+ expect(changes.size).to eq(9)
+ expect(changes.first.operation).to eq(:modified)
+ expect(changes.first.new_path).to eq('.gitmodules')
+ expect(changes.last.operation).to eq(:added)
+ expect(changes.last.new_path).to eq('files/lfs/picture-invalid.png')
+ end
+ end
+ end
+
describe '#merge_base' do
shared_examples '#merge_base' do
where(:from, :to, :result) do
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 9be3fa633a7..7951cbe7b1d 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::GitalyClient::CommitService do
initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863').raw
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
- left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ left_commit_id: Gitlab::Git::EMPTY_TREE_ID,
right_commit_id: initial_commit.id,
collapse_diffs: true,
enforce_limits: true,
@@ -77,7 +77,7 @@ describe Gitlab::GitalyClient::CommitService do
initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
request = Gitaly::CommitDeltaRequest.new(
repository: repository_message,
- left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ left_commit_id: Gitlab::Git::EMPTY_TREE_ID,
right_commit_id: initial_commit.id
)
@@ -90,7 +90,7 @@ describe Gitlab::GitalyClient::CommitService do
describe '#between' do
let(:from) { 'master' }
- let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+ let(:to) { Gitlab::Git::EMPTY_TREE_ID }
it 'sends an RPC request' do
request = Gitaly::CommitsBetweenRequest.new(
@@ -155,7 +155,7 @@ describe Gitlab::GitalyClient::CommitService do
end
describe '#find_commit' do
- let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+ let(:revision) { Gitlab::Git::EMPTY_TREE_ID }
it 'sends an RPC request' do
request = Gitaly::FindCommitRequest.new(
repository: repository_message, revision: revision
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 21592688bf0..074323d47d2 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -84,6 +84,17 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#info_attributes' do
+ it 'reads the info attributes' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:get_info_attributes)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([])
+
+ client.info_attributes
+ end
+ end
+
describe '#has_local_branches?' do
it 'sends a has_local_branches message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 71a743495a2..4ba99009855 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe Gitlab::Utils do
- delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, to: :described_class
+ delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string,
+ :bytes_to_megabytes, to: :described_class
describe '.slugify' do
{
@@ -97,4 +98,12 @@ describe Gitlab::Utils do
expect(ensure_array_from_string(str)).to eq(%w[seven eight 9 10])
end
end
+
+ describe '.bytes_to_megabytes' do
+ it 'converts bytes to megabytes' do
+ bytes = 1.megabyte
+
+ expect(bytes_to_megabytes(bytes)).to eq(1)
+ end
+ end
end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 83556787e85..4a71b1feebd 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -16,23 +16,25 @@ describe RspecFlaky::Config, :aggregate_failures do
end
end
- context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'false'" do
- before do
- stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
- end
-
- it 'returns false' do
- expect(described_class).not_to be_generate_report
+ context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:env_value, :result) do
+ '1' | true
+ 'true' | true
+ 'foo' | false
+ '0' | false
+ 'false' | false
end
- end
- context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'true'" do
- before do
- stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
- end
+ with_them do
+ before do
+ stub_env('FLAKY_RSPEC_GENERATE_REPORT', env_value)
+ end
- it 'returns true' do
- expect(described_class).to be_generate_report
+ it 'returns false' do
+ expect(described_class.generate_report?).to be(result)
+ end
end
end
end
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 06a8ba0d02e..6731a27ed17 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -24,14 +24,6 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
}
end
- describe '.from_json' do
- it 'accepts a JSON' do
- collection = described_class.from_json(JSON.pretty_generate(collection_hash))
-
- expect(collection.to_report).to eq(described_class.new(collection_hash).to_report)
- end
- end
-
describe '#initialize' do
it 'accepts no argument' do
expect { described_class.new }.not_to raise_error
@@ -46,11 +38,11 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
end
end
- describe '#to_report' do
+ describe '#to_h' do
it 'calls #to_h on the values' do
collection = described_class.new(collection_hash)
- expect(collection.to_report).to eq(collection_report)
+ expect(collection.to_h).to eq(collection_report)
end
end
@@ -61,7 +53,7 @@ describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
a: { example_id: 'spec/foo/bar_spec.rb:2' },
c: { example_id: 'spec/bar/baz_spec.rb:4' })
- expect((collection2 - collection1).to_report).to eq(
+ expect((collection2 - collection1).to_h).to eq(
c: {
example_id: 'spec/bar/baz_spec.rb:4',
first_flaky_at: nil,
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index bfb7648b486..ef085445081 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -4,7 +4,7 @@ describe RspecFlaky::Listener, :aggregate_failures do
let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' }
let(:suite_flaky_example_report) do
{
- already_flaky_example_uid => {
+ "#{already_flaky_example_uid}": {
example_id: 'spec/foo/bar_spec.rb:2',
file: 'spec/foo/bar_spec.rb',
line: 2,
@@ -55,8 +55,7 @@ describe RspecFlaky::Listener, :aggregate_failures do
it 'returns a valid Listener instance' do
listener = described_class.new
- expect(listener.to_report(listener.suite_flaky_examples))
- .to eq(expected_suite_flaky_examples)
+ expect(listener.suite_flaky_examples.to_h).to eq(expected_suite_flaky_examples)
expect(listener.flaky_examples).to eq({})
end
end
@@ -65,25 +64,35 @@ describe RspecFlaky::Listener, :aggregate_failures do
it_behaves_like 'a valid Listener instance'
end
- context 'when a report file exists and set by SUITE_FLAKY_RSPEC_REPORT_PATH' do
- let(:report_file) do
- Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
- f.write(JSON.pretty_generate(suite_flaky_example_report))
- f.rewind
- end
- end
+ context 'when SUITE_FLAKY_RSPEC_REPORT_PATH is set' do
+ let(:report_file_path) { 'foo/report.json' }
before do
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file.path)
+ stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file_path)
end
- after do
- report_file.close
- report_file.unlink
+ context 'and report file exists' do
+ before do
+ expect(File).to receive(:exist?).with(report_file_path).and_return(true)
+ end
+
+ it 'delegates the load to RspecFlaky::Report' do
+ report = RspecFlaky::Report.new(RspecFlaky::FlakyExamplesCollection.new(suite_flaky_example_report))
+
+ expect(RspecFlaky::Report).to receive(:load).with(report_file_path).and_return(report)
+ expect(described_class.new.suite_flaky_examples.to_h).to eq(report.flaky_examples.to_h)
+ end
end
- it_behaves_like 'a valid Listener instance' do
- let(:expected_suite_flaky_examples) { suite_flaky_example_report }
+ context 'and report file does not exist' do
+ before do
+ expect(File).to receive(:exist?).with(report_file_path).and_return(false)
+ end
+
+ it 'return an empty hash' do
+ expect(RspecFlaky::Report).not_to receive(:load)
+ expect(described_class.new.suite_flaky_examples.to_h).to eq({})
+ end
end
end
end
@@ -186,74 +195,21 @@ describe RspecFlaky::Listener, :aggregate_failures do
let(:notification_already_flaky_rspec_example) { double(example: already_flaky_rspec_example) }
context 'when a report file path is set by FLAKY_RSPEC_REPORT_PATH' do
- let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
- let(:new_report_file_path) { Rails.root.join('tmp', 'rspec_flaky_new_report.json') }
+ it 'delegates the writes to RspecFlaky::Report' do
+ listener.example_passed(notification_new_flaky_rspec_example)
+ listener.example_passed(notification_already_flaky_rspec_example)
- before do
- stub_env('FLAKY_RSPEC_REPORT_PATH', report_file_path)
- stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', new_report_file_path)
- FileUtils.rm(report_file_path) if File.exist?(report_file_path)
- FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
- end
+ report1 = double
+ report2 = double
- after do
- FileUtils.rm(report_file_path) if File.exist?(report_file_path)
- FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path)
- end
+ expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples).and_return(report1)
+ expect(report1).to receive(:write).with(RspecFlaky::Config.flaky_examples_report_path)
- context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do
- before do
- stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false')
- end
-
- it 'does not write any report file' do
- listener.example_passed(notification_new_flaky_rspec_example)
+ expect(RspecFlaky::Report).to receive(:new).with(listener.flaky_examples - listener.suite_flaky_examples).and_return(report2)
+ expect(report2).to receive(:write).with(RspecFlaky::Config.new_flaky_examples_report_path)
- listener.dump_summary(nil)
-
- expect(File.exist?(report_file_path)).to be(false)
- expect(File.exist?(new_report_file_path)).to be(false)
- end
+ listener.dump_summary(nil)
end
-
- context 'when FLAKY_RSPEC_GENERATE_REPORT == "true"' do
- before do
- stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true')
- end
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- it 'writes the report files' do
- listener.example_passed(notification_new_flaky_rspec_example)
- listener.example_passed(notification_already_flaky_rspec_example)
-
- listener.dump_summary(nil)
-
- expect(File.exist?(report_file_path)).to be(true)
- expect(File.exist?(new_report_file_path)).to be(true)
-
- expect(File.read(report_file_path))
- .to eq(JSON.pretty_generate(listener.to_report(listener.flaky_examples)))
-
- new_example = RspecFlaky::Example.new(notification_new_flaky_rspec_example)
- new_flaky_example = RspecFlaky::FlakyExample.new(new_example)
- new_flaky_example.update_flakiness!
-
- expect(File.read(new_report_file_path))
- .to eq(JSON.pretty_generate(listener.to_report(new_example.uid => new_flaky_example)))
- end
- end
- end
- end
-
- describe '#to_report' do
- let(:listener) { described_class.new(suite_flaky_example_report.to_json) }
-
- it 'transforms the internal hash to a JSON-ready hash' do
- expect(listener.to_report(already_flaky_example_uid => already_flaky_example))
- .to match(hash_including(suite_flaky_example_report))
end
end
end
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
new file mode 100644
index 00000000000..7d57d99f7e5
--- /dev/null
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+describe RspecFlaky::Report, :aggregate_failures do
+ let(:a_hundred_days) { 3600 * 24 * 100 }
+ let(:collection_hash) do
+ {
+ a: { example_id: 'spec/foo/bar_spec.rb:2' },
+ b: { example_id: 'spec/foo/baz_spec.rb:3', first_flaky_at: (Time.now - a_hundred_days).to_s, last_flaky_at: (Time.now - a_hundred_days).to_s }
+ }
+ end
+ let(:suite_flaky_example_report) do
+ {
+ '6e869794f4cfd2badd93eb68719371d1': {
+ example_id: 'spec/foo/bar_spec.rb:2',
+ file: 'spec/foo/bar_spec.rb',
+ line: 2,
+ description: 'hello world',
+ first_flaky_at: 1234,
+ last_flaky_at: 4321,
+ last_attempts_count: 3,
+ flaky_reports: 1,
+ last_flaky_job: nil
+ }
+ }
+ end
+ let(:flaky_examples) { RspecFlaky::FlakyExamplesCollection.new(collection_hash) }
+ let(:report) { described_class.new(flaky_examples) }
+
+ describe '.load' do
+ let!(:report_file) do
+ Tempfile.new(%w[rspec_flaky_report .json]).tap do |f|
+ f.write(JSON.pretty_generate(suite_flaky_example_report))
+ f.rewind
+ end
+ end
+
+ after do
+ report_file.close
+ report_file.unlink
+ end
+
+ it 'loads the report file' do
+ expect(described_class.load(report_file.path).flaky_examples.to_h).to eq(suite_flaky_example_report)
+ end
+ end
+
+ describe '.load_json' do
+ let(:report_json) do
+ JSON.pretty_generate(suite_flaky_example_report)
+ end
+
+ it 'loads the report file' do
+ expect(described_class.load_json(report_json).flaky_examples.to_h).to eq(suite_flaky_example_report)
+ end
+ end
+
+ describe '#initialize' do
+ it 'accepts a RspecFlaky::FlakyExamplesCollection' do
+ expect { report }.not_to raise_error
+ end
+
+ it 'does not accept anything else' do
+ expect { described_class.new([1, 2, 3]) }.to raise_error(ArgumentError, "`flaky_examples` must be a RspecFlaky::FlakyExamplesCollection, Array given!")
+ end
+ end
+
+ it 'delegates to #flaky_examples using SimpleDelegator' do
+ expect(report.__getobj__).to eq(flaky_examples)
+ end
+
+ describe '#write' do
+ let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') }
+
+ before do
+ FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ end
+
+ after do
+ FileUtils.rm(report_file_path) if File.exist?(report_file_path)
+ end
+
+ context 'when RspecFlaky::Config.generate_report? is false' do
+ before do
+ allow(RspecFlaky::Config).to receive(:generate_report?).and_return(false)
+ end
+
+ it 'does not write any report file' do
+ report.write(report_file_path)
+
+ expect(File.exist?(report_file_path)).to be(false)
+ end
+ end
+
+ context 'when RspecFlaky::Config.generate_report? is true' do
+ before do
+ allow(RspecFlaky::Config).to receive(:generate_report?).and_return(true)
+ end
+
+ it 'delegates the writes to RspecFlaky::Report' do
+ report.write(report_file_path)
+
+ expect(File.exist?(report_file_path)).to be(true)
+ expect(File.read(report_file_path))
+ .to eq(JSON.pretty_generate(report.flaky_examples.to_h))
+ end
+ end
+ end
+
+ describe '#prune_outdated' do
+ it 'returns a new collection without the examples older than 90 days by default' do
+ new_report = flaky_examples.to_h.dup.tap { |r| r.delete(:b) }
+ new_flaky_examples = report.prune_outdated
+
+ expect(new_flaky_examples).to be_a(described_class)
+ expect(new_flaky_examples.to_h).to eq(new_report)
+ expect(flaky_examples).to have_key(:b)
+ end
+
+ it 'accepts a given number of days' do
+ new_flaky_examples = report.prune_outdated(days: 200)
+
+ expect(new_flaky_examples.to_h).to eq(report.to_h)
+ end
+ end
+end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 461e754dc1f..5326f9cb8c0 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -51,7 +51,11 @@ describe BroadcastMessage do
expect(described_class).to receive(:where).and_call_original.once
- 2.times { described_class.current }
+ described_class.current
+
+ Timecop.travel(1.year) do
+ described_class.current
+ end
end
it 'includes messages that need to be displayed in the future' do
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index d87c1ca14f0..374a157bec0 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -172,11 +172,12 @@ describe ProjectWiki do
describe '#find_file' do
shared_examples 'finding a wiki file' do
+ let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
+
before do
- file = File.open(Rails.root.join('spec', 'fixtures', 'dk.png'))
subject.wiki # Make sure the wiki repo exists
- BareRepoOperations.new(subject.repository.path_to_repo).commit_file(file, 'image.png')
+ BareRepoOperations.new(subject.repository.path_to_repo).commit_file(image, 'image.png')
end
it 'returns the latest version of the file if it exists' do
@@ -192,6 +193,13 @@ describe ProjectWiki do
file = subject.find_file('image.png')
expect(file).to be_a Gitlab::Git::WikiFile
end
+
+ it 'returns the whole file' do
+ file = subject.find_file('image.png')
+ image.rewind
+
+ expect(file.raw_data.b).to eq(image.read.b)
+ end
end
context 'when Gitaly wiki_find_file is enabled' do
diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb
index b4a4a44d07b..075cb45e46c 100644
--- a/spec/services/events/render_service_spec.rb
+++ b/spec/services/events/render_service_spec.rb
@@ -9,9 +9,7 @@ describe Events::RenderService do
context 'when the request format is atom' do
it 'renders the note inside events' do
expect(Banzai::ObjectRenderer).to receive(:new)
- .with(event.project, user,
- only_path: false,
- xhtml: true)
+ .with(user: user, redaction_context: { only_path: false, xhtml: true })
.and_call_original
expect_any_instance_of(Banzai::ObjectRenderer)
@@ -24,7 +22,7 @@ describe Events::RenderService do
context 'when the request format is not atom' do
it 'renders the note inside events' do
expect(Banzai::ObjectRenderer).to receive(:new)
- .with(event.project, user, {})
+ .with(user: user, redaction_context: {})
.and_call_original
expect_any_instance_of(Banzai::ObjectRenderer)
diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb
index faac498037f..f771620bc0d 100644
--- a/spec/services/notes/render_service_spec.rb
+++ b/spec/services/notes/render_service_spec.rb
@@ -4,23 +4,28 @@ describe Notes::RenderService do
describe '#execute' do
it 'renders a Note' do
note = double(:note)
- project = double(:project)
wiki = double(:wiki)
user = double(:user)
- expect(Banzai::ObjectRenderer).to receive(:new)
- .with(project, user,
- requested_path: 'foo',
- project_wiki: wiki,
- ref: 'bar',
- only_path: nil,
- xhtml: false)
+ expect(Banzai::ObjectRenderer)
+ .to receive(:new)
+ .with(
+ user: user,
+ redaction_context: {
+ requested_path: 'foo',
+ project_wiki: wiki,
+ ref: 'bar',
+ only_path: nil,
+ xhtml: false
+ }
+ )
.and_call_original
expect_any_instance_of(Banzai::ObjectRenderer)
- .to receive(:render).with([note], :note)
+ .to receive(:render)
+ .with([note], :note)
- described_class.new(user).execute([note], project,
+ described_class.new(user).execute([note],
requested_path: 'foo',
project_wiki: wiki,
ref: 'bar',
diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb
index 8eeaa37d3c5..3f4a4243cb6 100644
--- a/spec/support/bare_repo_operations.rb
+++ b/spec/support/bare_repo_operations.rb
@@ -1,19 +1,15 @@
require 'zlib'
class BareRepoOperations
- # The ID of empty tree.
- # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
- EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
-
include Gitlab::Popen
def initialize(path_to_repo)
@path_to_repo = path_to_repo
end
- def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID)
+ def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID)
commit_tree_args = ['commit-tree', tree_id, '-m', msg]
- commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID
+ commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID
commit_id = execute(commit_tree_args)
commit_id[0]
@@ -21,7 +17,7 @@ class BareRepoOperations
# Based on https://stackoverflow.com/a/25556917/1856239
def commit_file(file, dst_path, branch = 'master')
- head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID
+ head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID
execute(['read-tree', '--empty'])
execute(['read-tree', head_id])
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index b871b7ffc90..721d359c2ee 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -18,6 +18,11 @@ module FilterSpecHelper
context.reverse_merge!(project: project)
end
+ render_context = Banzai::RenderContext
+ .new(context[:project], context[:current_user])
+
+ context = context.merge(render_context: render_context)
+
described_class.call(html, context)
end
diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb
new file mode 100644
index 00000000000..3525d9a70a7
--- /dev/null
+++ b/spec/support/helpers/features/branches_helpers.rb
@@ -0,0 +1,33 @@
+# These helpers allow you to manipulate with sorting features.
+#
+# Usage:
+# describe "..." do
+# include Spec::Support::Helpers::Features::BranchesHelpers
+# ...
+#
+# create_branch("feature")
+# select_branch("master")
+#
+module Spec
+ module Support
+ module Helpers
+ module Features
+ module BranchesHelpers
+ def create_branch(branch_name, source_branch_name = "master")
+ fill_in("branch_name", with: branch_name)
+ select_branch(source_branch_name)
+ click_button("Create branch")
+ end
+
+ def select_branch(branch_name)
+ find(".git-revision-dropdown-toggle").click
+
+ page.within("#new-branch-form .dropdown-menu") do
+ click_link(branch_name)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/reference_parser_helpers.rb b/spec/support/reference_parser_helpers.rb
index 5d5e80851e6..c01897ed1a1 100644
--- a/spec/support/reference_parser_helpers.rb
+++ b/spec/support/reference_parser_helpers.rb
@@ -5,9 +5,11 @@ module ReferenceParserHelpers
shared_examples 'no N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
+ context = Banzai::RenderContext.new(project, user)
+
record_queries = lambda do |links|
ActiveRecord::QueryRecorder.new do
- described_class.new(project, user).nodes_visible_to_user(user, links)
+ described_class.new(context).nodes_visible_to_user(user, links)
end
end
@@ -19,9 +21,11 @@ module ReferenceParserHelpers
end
it 'avoids N+1 queries in #records_for_nodes', :request_store do
+ context = Banzai::RenderContext.new(project, user)
+
record_queries = lambda do |links|
ActiveRecord::QueryRecorder.new do
- described_class.new(project, user).records_for_nodes(links)
+ described_class.new(context).records_for_nodes(links)
end
end
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index d57137223ed..39b6783cef8 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -37,8 +37,10 @@ captures/
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
+.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
+.idea/caches
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
diff --git a/vendor/gitignore/Elixir.gitignore b/vendor/gitignore/Elixir.gitignore
index b6d65867dac..86e4c3f3905 100644
--- a/vendor/gitignore/Elixir.gitignore
+++ b/vendor/gitignore/Elixir.gitignore
@@ -6,3 +6,4 @@
erl_crash.dump
*.ez
*.beam
+/config/*.secret.exs
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index 9c01e12b050..a83a428c844 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -1,12 +1,12 @@
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-# User-specific stuff:
+# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
-# Sensitive or high-churn files:
+# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
@@ -14,7 +14,7 @@
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
-# Gradle:
+# Gradle
.idea/**/gradle.xml
.idea/**/libraries
@@ -22,14 +22,12 @@
cmake-build-debug/
cmake-build-release/
-# Mongo Explorer plugin:
+# Mongo Explorer plugin
.idea/**/mongoSettings.xml
-## File-based project format:
+# File-based project format
*.iws
-## Plugin-specific files:
-
# IntelliJ
out/
@@ -47,3 +45,6 @@ com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore
index 846a1db836c..0251dd21ad8 100644
--- a/vendor/gitignore/Global/Windows.gitignore
+++ b/vendor/gitignore/Global/Windows.gitignore
@@ -15,6 +15,7 @@ $RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
+*.msix
*.msm
*.msp
diff --git a/vendor/gitignore/Godot.gitignore b/vendor/gitignore/Godot.gitignore
new file mode 100644
index 00000000000..ba45ca4582e
--- /dev/null
+++ b/vendor/gitignore/Godot.gitignore
@@ -0,0 +1,8 @@
+
+# Godot-specific ignores
+.import/
+export.cfg
+export_presets.cfg
+
+# Mono-specific ignores
+.mono/
diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore
index b6bf3a9c96a..378c158bddf 100644
--- a/vendor/gitignore/Joomla.gitignore
+++ b/vendor/gitignore/Joomla.gitignore
@@ -1,4 +1,3 @@
-/.gitignore
/.htaccess
/administrator/cache/*
/administrator/components/com_admin/*
diff --git a/vendor/gitignore/KiCad.gitignore b/vendor/gitignore/KiCad.gitignore
index 208bc4fc591..198392e551e 100644
--- a/vendor/gitignore/KiCad.gitignore
+++ b/vendor/gitignore/KiCad.gitignore
@@ -1,4 +1,5 @@
# For PCBs designed using KiCad: http://www.kicad-pcb.org/
+# Format documentation: http://kicad-pcb.org/help/file-formats/
# Temporary files
*.000
@@ -8,6 +9,10 @@
*~
_autosave-*
*.tmp
+*-cache.lib
+*-rescue.lib
+*-save.pro
+*-save.kicad_pcb
# Netlist files (exported from Eeschema)
*.net
diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore
index a9fe6fba80d..a4cb69a32cc 100644
--- a/vendor/gitignore/Leiningen.gitignore
+++ b/vendor/gitignore/Leiningen.gitignore
@@ -11,3 +11,4 @@ pom.xml.asc
.lein-plugins/
.lein-failures
.nrepl-port
+.cpcache/
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index d1bed128fa8..ad46b30886f 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -36,7 +36,7 @@ build/Release
node_modules/
jspm_packages/
-# Typescript v1 declaration files
+# TypeScript v1 declaration files
typings/
# Optional npm cache directory
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index b989be6ca15..894a44cc066 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -53,9 +53,8 @@ coverage.xml
# Django stuff:
*.log
-.static_storage/
-.media/
local_settings.py
+db.sqlite3
# Flask stuff:
instance/
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 828ab1d556a..e62f78e17bc 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -14,6 +14,7 @@ pickle-email-*.html
# TODO Comment out this rule if you are OK with secrets being uploaded to the repo
config/initializers/secret_token.rb
+config/master.key
# Only include if you have production secrets in this file, which is no longer a Rails default
# config/secrets.yml
diff --git a/vendor/gitignore/Rust.gitignore b/vendor/gitignore/Rust.gitignore
index 50281a44270..088ba6ba7d3 100644
--- a/vendor/gitignore/Rust.gitignore
+++ b/vendor/gitignore/Rust.gitignore
@@ -3,7 +3,7 @@
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 78c1c5cd26e..c560658e45c 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -153,7 +153,9 @@ _minted*
*.mw
# nomencl
+*.nlg
*.nlo
+*.nls
# pax
*.pax
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 75e5b1405da..a7c0c70a0b4 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -5,7 +5,7 @@
[Bb]uilds/
Assets/AssetStoreTools*
-# Visual Studio 2015 cache directory
+# Visual Studio cache directory
/.vs/
# Autogenerated VS/MD/Consulo solution and project files
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 8e930f59c47..29063cf6072 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -64,8 +64,10 @@ StyleCopReport.xml
*.ilk
*.meta
*.obj
+*.iobj
*.pch
*.pdb
+*.ipdb
*.pgc
*.pgd
*.rsp
@@ -248,6 +250,7 @@ ServiceFabricBackup/
*.rdl.data
*.bim.layout
*.bim_*.settings
+*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
@@ -319,3 +322,8 @@ ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 4223dc18933..4810035a9e3 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -50,9 +50,9 @@ stages:
build:
stage: build
- image: docker:git
+ image: docker:stable-git
services:
- - docker:dind
+ - docker:stable-dind
variables:
DOCKER_DRIVER: overlay2
script:
@@ -76,12 +76,12 @@ test:
- branches
codequality:
- image: docker:latest
+ image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- - docker:dind
+ - docker:stable-dind
script:
- setup_docker
- codeclimate
@@ -90,12 +90,12 @@ codequality:
performance:
stage: performance
- image: docker:latest
+ image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- - docker:dind
+ - docker:stable-dind
script:
- setup_docker
- performance
@@ -109,25 +109,37 @@ performance:
kubernetes: active
sast:
- image: docker:latest
+ image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- - docker:dind
+ - docker:stable-dind
script:
- setup_docker
- sast
artifacts:
paths: [gl-sast-report.json]
+dependency_scanning:
+ image: docker:stable
+ variables:
+ DOCKER_DRIVER: overlay2
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ script:
+ - setup_docker
+ - dependency_scanning
+ artifacts:
+ paths: [gl-dependency-scanning-report.json]
sast:container:
- image: docker:latest
+ image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- - docker:dind
+ - docker:stable-dind
script:
- setup_docker
- sast_container
@@ -324,7 +336,6 @@ production:
fi
docker run --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}" \
- --env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
@@ -335,6 +346,20 @@ production:
esac
}
+ function dependency_scanning() {
+ case "$CI_SERVER_VERSION" in
+ *-ee)
+ docker run --env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}" \
+ --volume "$PWD:/code" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
+ ;;
+ *)
+ echo "GitLab EE is required"
+ ;;
+ esac
+ }
+
function deploy() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
@@ -355,10 +380,16 @@ production:
if [[ "$track" == "stable" ]]; then
# for stable track get number of replicas from `PRODUCTION_REPLICAS`
eval new_replicas=\$${env_slug}_REPLICAS
+ if [[ -z "$new_replicas" ]]; then
+ new_replicas=$REPLICAS
+ fi
service_enabled="true"
else
# for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
+ if [[ -z "$new_replicas" ]]; then
+ eval new_replicas=\${env_track}_REPLICAS
+ fi
fi
if [[ -n "$new_replicas" ]]; then
replicas="$new_replicas"
diff --git a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
index 0ad662cf704..0688f77a1d2 100644
--- a/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Laravel.gitlab-ci.yml
@@ -32,7 +32,7 @@ before_script:
- apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq
# Install php extensions
- - docker-php-ext-install mbstring mcrypt pdo_mysql curl json intl gd xml zip bz2 opcache
+ - docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
# Install & enable Xdebug for code coverage reports
- pecl install xdebug
diff --git a/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml
new file mode 100644
index 00000000000..9df2a4797b2
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Gatsby.gitlab-ci.yml
@@ -0,0 +1,17 @@
+image: node:latest
+
+# This folder is cached between builds
+# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
+cache:
+ paths:
+ - node_modules/
+
+pages:
+ script:
+ - yarn install
+ - ./node_modules/.bin/gatsby build --prefix-paths
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
index a72b8281401..b8cfb0f56f6 100644
--- a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
@@ -1,5 +1,5 @@
# Full project: https://gitlab.com/pages/hugo
-image: publysher/hugo
+image: dettmering/hugo-build
pages:
script:
diff --git a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
index a2882a5407d..2e0589de652 100644
--- a/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Python.gitlab-ci.yml
@@ -1,8 +1,27 @@
-# This file is a template, and might need editing before it works on your project.
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/python/tags/
image: python:latest
+# Change pip's cache directory to be inside the project directory since we can
+# only cache local items.
+variables:
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
+
+# Pip's cache doesn't store the python packages
+# https://pip.pypa.io/en/stable/reference/pip_install/#caching
+#
+# If you want to also cache the installed packages, you have to install
+# them in a virtualenv and cache it as well.
+cache:
+ paths:
+ - .cache/pip
+ - venv/
+
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
+ - pip install virtualenv
+ - virtualenv venv
+ - source venv/bin/activate
test:
script:
diff --git a/yarn.lock b/yarn.lock
index 243b83f4471..c4d682016a0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1859,9 +1859,9 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
-commander@^2.13.0, commander@^2.9.0:
- version "2.14.1"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
+commander@^2.13.0, commander@^2.15.1, commander@^2.9.0:
+ version "2.15.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
commondir@^1.0.1:
version "1.0.1"