summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--.rubocop.yml18
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--PROCESS.md16
-rw-r--r--app/assets/javascripts/api.js1
-rw-r--r--app/assets/javascripts/commits.js2
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js4
-rw-r--r--app/assets/javascripts/dispatcher.js2
-rw-r--r--app/assets/javascripts/fly_out_nav.js8
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js2
-rw-r--r--app/assets/javascripts/main.js4
-rw-r--r--app/assets/javascripts/new_sidebar.js6
-rw-r--r--app/assets/javascripts/project.js4
-rw-r--r--app/assets/javascripts/render_gfm.js4
-rw-r--r--app/assets/javascripts/repo/components/repo.vue55
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue90
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue37
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue104
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue67
-rw-r--r--app/assets/javascripts/repo/components/repo_file_buttons.vue47
-rw-r--r--app/assets/javascripts/repo/components/repo_file_options.vue2
-rw-r--r--app/assets/javascripts/repo/components/repo_loading_file.vue61
-rw-r--r--app/assets/javascripts/repo/components/repo_prev_directory.vue16
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue8
-rw-r--r--app/assets/javascripts/repo/components/repo_tab.vue12
-rw-r--r--app/assets/javascripts/repo/components/repo_tabs.vue14
-rw-r--r--app/assets/javascripts/repo/helpers/monaco_loader_helper.js3
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js75
-rw-r--r--app/assets/javascripts/repo/index.js5
-rw-r--r--app/assets/javascripts/repo/services/repo_service.js14
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js50
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/popup_dialog.vue64
-rw-r--r--app/assets/stylesheets/framework/animations.scss78
-rw-r--r--app/assets/stylesheets/framework/layout.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss10
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/new_nav.scss1
-rw-r--r--app/assets/stylesheets/new_sidebar.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss5
-rw-r--r--app/assets/stylesheets/pages/repo.scss149
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/groups/application_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/projects/blob_controller.rb5
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb24
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/helpers/commits_helper.rb6
-rw-r--r--app/helpers/import_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb13
-rw-r--r--app/helpers/milestones_helper.rb13
-rw-r--r--app/helpers/pipeline_schedules_helper.rb10
-rw-r--r--app/helpers/projects_helper.rb9
-rw-r--r--app/helpers/version_check_helper.rb2
-rw-r--r--app/models/blob_viewer/notebook.rb2
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb6
-rw-r--r--app/models/concerns/internal_id.rb2
-rw-r--r--app/models/concerns/mentionable.rb4
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/project_features_compatibility.rb2
-rw-r--r--app/models/deploy_keys_project.rb2
-rw-r--r--app/models/network/commit.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/protectable_dropdown.rb8
-rw-r--r--app/models/redirect_route.rb2
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/models/user.rb2
-rw-r--r--app/serializers/project_entity.rb2
-rw-r--r--app/serializers/tree_root_entity.rb15
-rw-r--r--app/services/akismet_service.rb2
-rw-r--r--app/services/ci/retry_build_service.rb2
-rw-r--r--app/services/commits/change_service.rb1
-rw-r--r--app/services/issuable_base_service.rb2
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/test_hooks/base_service.rb2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml271
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml153
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml153
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml479
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml6
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml2
-rw-r--r--app/views/shared/_ref_switcher.html.haml2
-rw-r--r--app/views/shared/_target_switcher.html.haml2
-rw-r--r--app/views/shared/icons/_express.svg (renamed from app/views/shared/icons/_node_express.svg)0
-rw-r--r--app/views/shared/icons/_spring.svg (renamed from app/views/shared/icons/_java_spring.svg)0
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/repo/_repo.html.haml9
-rw-r--r--app/workers/gitlab_shell_worker.rb2
-rw-r--r--changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml4
-rw-r--r--changelogs/unreleased/34643-fix-project-path-slugify.yml4
-rw-r--r--changelogs/unreleased/commits-list-page-limit.yml5
-rw-r--r--changelogs/unreleased/fix-btn-alignment.yml5
-rw-r--r--changelogs/unreleased/issue_31790.yml4
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--config/initializers/0_acts_as_taggable.rb2
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/6_validations.rb4
-rw-r--r--config/initializers/active_record_mysql_timestamp.rb30
-rw-r--r--config/initializers/static_files.rb10
-rw-r--r--config/initializers/trusted_proxies.rb2
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/webpack.config.js3
-rw-r--r--db/migrate/20161020075830_default_request_access_projects.rb2
-rw-r--r--db/post_migrate/20170503004427_update_retried_for_ci_build.rb2
-rw-r--r--db/post_migrate/20170523083112_migrate_old_artifacts.rb6
-rw-r--r--db/post_migrate/20170815060945_remove_duplicate_mr_events.rb26
-rw-r--r--db/schema.rb2
-rw-r--r--doc/README.md1
-rw-r--r--doc/articles/artifactory_and_gitlab/index.md278
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md2
-rw-r--r--doc/articles/how_to_install_git/index.md2
-rw-r--r--doc/articles/index.md1
-rw-r--r--doc/articles/openshift_and_gitlab/index.md2
-rw-r--r--doc/ci/README.md3
-rw-r--r--doc/development/README.md4
-rw-r--r--doc/development/database_merge_request_checklist.md15
-rw-r--r--doc/development/hash_indexes.md20
-rw-r--r--doc/development/ordering_table_columns.md127
-rw-r--r--doc/development/serializing_data.md3
-rw-r--r--doc/development/sql.md26
-rw-r--r--doc/development/verifying_database_capabilities.md26
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/user/permissions.md109
-rw-r--r--doc/user/search/img/group_issues_filter.pngbin0 -> 45288 bytes
-rw-r--r--doc/user/search/index.md8
-rw-r--r--features/steps/profile/emails.rb2
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/files.rb3
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/v3/notes.rb6
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb4
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb4
-rw-r--r--lib/banzai/object_renderer.rb2
-rw-r--r--lib/banzai/pipeline/base_pipeline.rb2
-rw-r--r--lib/banzai/renderer.rb4
-rw-r--r--lib/bitbucket/collection.rb2
-rw-r--r--lib/ci/ansi2html.rb2
-rw-r--r--lib/constraints/project_url_constrainer.rb2
-rw-r--r--lib/declarative_policy/base.rb4
-rw-r--r--lib/declarative_policy/dsl.rb2
-rw-r--r--lib/file_size_validator.rb4
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/auth/ip_rate_limiter.rb12
-rw-r--r--lib/gitlab/cache/request_cache.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/diff/line_mapper.rb6
-rw-r--r--lib/gitlab/git/blob.rb4
-rw-r--r--lib/gitlab/git/repository.rb22
-rw-r--r--lib/gitlab/git/tree.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/github_import/base_formatter.rb4
-rw-r--r--lib/gitlab/github_import/client.rb2
-rw-r--r--lib/gitlab/github_import/importer.rb2
-rw-r--r--lib/gitlab/import_export/attributes_finder.rb1
-rw-r--r--lib/gitlab/lazy.rb2
-rw-r--r--lib/gitlab/ldap/auth_hash.rb2
-rw-r--r--lib/gitlab/ldap/person.rb4
-rw-r--r--lib/gitlab/markdown/pipeline.rb2
-rw-r--r--lib/gitlab/middleware/rails_queue_duration.rb2
-rw-r--r--lib/gitlab/project_template.rb4
-rw-r--r--lib/gitlab/redis/cache.rb5
-rw-r--r--lib/gitlab/redis/queues.rb5
-rw-r--r--lib/gitlab/redis/shared_state.rb5
-rw-r--r--lib/gitlab/redis/wrapper.rb13
-rw-r--r--lib/gitlab/shell.rb12
-rw-r--r--lib/gitlab/slash_commands/presenters/help.rb2
-rw-r--r--lib/gitlab/utils.rb13
-rw-r--r--lib/gitlab/workhorse.rb1
-rw-r--r--lib/tasks/gitlab/gitaly.rake2
-rw-r--r--lib/tasks/gitlab/update_templates.rake9
-rw-r--r--lib/uploaded_file.rb2
-rw-r--r--package.json2
-rw-r--r--qa/qa/runtime/release.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb29
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb12
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb10
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb18
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb20
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb192
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb4
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb10
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb58
-rw-r--r--spec/features/milestones/show_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb2
-rw-r--r--spec/features/search_spec.rb12
-rw-r--r--spec/helpers/projects_helper_spec.rb12
-rw-r--r--spec/helpers/version_check_helper_spec.rb2
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb2
-rw-r--r--spec/javascripts/fixtures/services.rb1
-rw-r--r--spec/javascripts/fly_out_nav_spec.js6
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js58
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js18
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js51
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js19
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js6
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js2
-rw-r--r--spec/javascripts/repo/components/repo_sidebar_spec.js1
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js14
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js12
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js2
-rw-r--r--spec/lib/file_size_validator_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb4
-rw-r--r--spec/lib/gitlab/git/storage/circuit_breaker_spec.rb59
-rw-r--r--spec/lib/gitlab/key_fingerprint_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb12
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/project_template_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb7
-rw-r--r--spec/lib/gitlab/shell_spec.rb35
-rw-r--r--spec/lib/gitlab/utils_spec.rb16
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb4
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb26
-rw-r--r--spec/models/ci/build_spec.rb2
-rw-r--r--spec/models/protectable_dropdown_spec.rb7
-rw-r--r--spec/requests/api/commits_spec.rb213
-rw-r--r--spec/requests/api/files_spec.rb10
-rw-r--r--spec/requests/api/protected_branches_spec.rb4
-rw-r--r--spec/requests/api/settings_spec.rb2
-rw-r--r--spec/requests/ci/api/builds_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb10
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/support/filtered_search_helpers.rb26
-rwxr-xr-xspec/support/generate-seed-repo-rb12
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb2
-rw-r--r--spec/support/stub_configuration.rb5
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb2
-rw-r--r--vendor/project_templates/express.tar.gzbin0 -> 4572 bytes
-rw-r--r--vendor/project_templates/spring.tar.gzbin0 -> 49882 bytes
-rw-r--r--yarn.lock6
255 files changed, 2769 insertions, 1625 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cd19b6f47ff..df7244d5a2e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-7.1-postgresql-9.6"
.default-cache: &default-cache
key: "ruby-233-with-yarn"
@@ -474,7 +474,6 @@ db:rollback-mysql:
variables:
SIZE: "1"
SETUP_DB: "false"
- RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
@@ -523,7 +522,7 @@ karma:
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-59.0-node-7.1-postgresql-9.6"
stage: test
variables:
BABEL_ENV: "coverage"
diff --git a/.rubocop.yml b/.rubocop.yml
index d25b4ac39c9..23bb0fa8be8 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -251,6 +251,10 @@ Layout/Tab:
Layout/TrailingBlankLines:
Enabled: true
+# Avoid trailing whitespace.
+Layout/TrailingWhitespace:
+ Enabled: true
+
# Style #######################################################################
# Check the naming of accessor methods for get_/set_.
@@ -1174,29 +1178,33 @@ RSpec/VerifiedDoubles:
GitlabSecurity/DeepMunge:
Enabled: true
Exclude:
- - 'spec/**/*'
- 'lib/**/*.rake'
+ - 'spec/**/*'
GitlabSecurity/PublicSend:
Enabled: true
Exclude:
- - 'spec/**/*'
+ - 'config/**/*'
+ - 'db/**/*'
+ - 'features/**/*'
- 'lib/**/*.rake'
+ - 'qa/**/*'
+ - 'spec/**/*'
GitlabSecurity/RedirectToParamsUpdate:
Enabled: true
Exclude:
- - 'spec/**/*'
- 'lib/**/*.rake'
+ - 'spec/**/*'
GitlabSecurity/SqlInjection:
Enabled: true
Exclude:
- - 'spec/**/*'
- 'lib/**/*.rake'
+ - 'spec/**/*'
GitlabSecurity/SystemCommandInjection:
Enabled: true
Exclude:
- - 'spec/**/*'
- 'lib/**/*.rake'
+ - 'spec/**/*'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index cf14285ec2a..4b4f14efea4 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -57,11 +57,6 @@ Layout/SpaceInsideParens:
Layout/SpaceInsidePercentLiteralDelimiters:
Enabled: false
-# Offense count: 89
-# Cop supports --auto-correct.
-Layout/TrailingWhitespace:
- Enabled: false
-
# Offense count: 272
RSpec/EmptyLineAfterFinalLet:
Enabled: false
diff --git a/Gemfile b/Gemfile
index a484cefb9a4..d10269d7aac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -232,7 +232,7 @@ gem 'ace-rails-ap', '~> 4.1.0'
gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding
-gem 'charlock_holmes', '~> 0.7.3'
+gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON
gem 'oj', '~> 2.17.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index a93caba2393..f7ad7bcbc6e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -117,7 +117,7 @@ GEM
activesupport (>= 4.0.0)
mime-types (>= 1.16)
cause (0.1)
- charlock_holmes (0.7.3)
+ charlock_holmes (0.7.5)
chronic (0.10.2)
chronic_duration (0.10.6)
numerizer (~> 0.1.1)
@@ -986,7 +986,7 @@ DEPENDENCIES
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.1)
- charlock_holmes (~> 0.7.3)
+ charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
concurrent-ruby (~> 1.0.5)
diff --git a/PROCESS.md b/PROCESS.md
index e5b17784d20..538e4389e00 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -141,18 +141,22 @@ the stable branch are:
* Fixes for security issues
* New or updated translations (as long as they do not touch application code)
-Any merge requests cherry-picked into the stable branch for a previous release
-will also be picked into the latest stable branch. These fixes will be shipped
-in the next RC for that release if it is before the 22nd. If the fixes are are
-completed on or after the 22nd, they will be shipped in a patch for that
-release.
-
During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set, so that release managers can find and pick them.
Merge requests without a milestone and this label will
not be merged into any stable branches.
+Fixes marked like this will be shipped in the next RC for that release. Once
+the final RC has been prepared ready for release on the 22nd, further fixes
+marked ~"Pick into Stable" will go into a patch for that release.
+
+If a merge request is to be picked into more than one release it will also need
+the ~"Pick into Backports" label set to remind the release manager to change
+the milestone after cherry-picking. As before, it should still have the
+~"Pick into Stable" label and the milestone of the highest release it will be
+picked into.
+
### Asking for an exception
If you think a merge request should go into an RC or patch even though it does not meet these requirements,
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 76b724e1bcb..56f91e95bb9 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -97,7 +97,6 @@ const Api = {
},
commitMultiple(id, data, callback) {
- // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
.replace(':id', id);
return $.ajax({
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 2b0bf49cf92..047544b1762 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -17,7 +17,7 @@ window.CommitsList = (function() {
}
});
- Pager.init(limit, false, false, this.processCommits);
+ Pager.init(parseInt(limit, 10), false, false, this.processCommits);
this.content = $("#commits-list");
this.searchField = $("#commits-search");
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index a2d33b0936e..5decfc1dc01 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -42,6 +42,10 @@ $(() => {
$components.each(function () {
const $this = $(this);
const noteId = $this.attr(':note-id');
+ const discussionId = $this.attr(':discussion-id');
+
+ if ($this.is('comment-and-resolve-btn') && !discussionId) return;
+
const tmp = Vue.extend({
template: $this.get(0).outerHTML
});
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index de47485c9f2..a0ed5c23ffe 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -644,7 +644,7 @@ import initChangesDropdown from './init_changes_dropdown';
return Dispatcher;
})();
- $(function() {
+ $(window).on('load', function() {
new Dispatcher();
});
}).call(window);
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index cbc3ad23990..32cb42c8b10 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -15,6 +15,10 @@ export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
+let headerHeight = 50;
+
+export const getHeaderHeight = () => headerHeight;
+
export const canShowActiveSubItems = (el) => {
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
@@ -74,7 +78,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list');
- subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; // eslint-disable-line no-param-reassign
+ subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect();
@@ -153,6 +157,8 @@ export default () => {
}, getHideSubItemsInterval());
});
+ headerHeight = document.querySelector('.nav-sidebar').offsetTop;
+
items.forEach((el) => {
const subItems = el.querySelector('.sidebar-sub-level-items');
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index 43a808b6ab3..ff2b66046b4 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -1,7 +1,7 @@
export const isSticky = (el, scrollY, stickyTop) => {
const top = el.offsetTop - scrollY;
- if (top === stickyTop) {
+ if (top <= stickyTop) {
el.classList.add('is-stuck');
} else {
el.classList.remove('is-stuck');
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 37f531c78f4..6d7c7e3c930 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -132,8 +132,9 @@ import './project_select';
import './project_show';
import './project_variables';
import './projects_list';
-import './render_gfm';
+import './syntax_highlight';
import './render_math';
+import './render_gfm';
import './right_sidebar';
import './search';
import './search_autocomplete';
@@ -141,7 +142,6 @@ import './smart_interval';
import './star';
import './subscription';
import './subscription_select';
-import './syntax_highlight';
import './dispatcher';
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
index b10b074f5ac..2d1ed9e4076 100644
--- a/app/assets/javascripts/new_sidebar.js
+++ b/app/assets/javascripts/new_sidebar.js
@@ -43,10 +43,12 @@ export default class NewNavSidebar {
}
toggleCollapsedSidebar(collapsed) {
- this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
+ const breakpoint = bp.getBreakpointSize();
+
if (this.$sidebar.length) {
+ this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$page.toggleClass('page-with-new-sidebar', !collapsed);
- this.$page.toggleClass('page-with-icon-sidebar', collapsed);
+ this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
NewNavSidebar.setCollapsedCookie(collapsed);
}
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 1c2100a1c25..d7e3ab42f00 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -126,11 +126,11 @@ import Cookies from 'js-cookie';
var $form = $dropdown.closest('form');
var $visit = $dropdown.data('visit');
- var shouldVisit = typeof $visit === 'undefined' ? true : $visit;
+ var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) {
- gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
+ gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`);
}
}
}
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index 2c3a9cacd38..bcdc0fd67b8 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -11,7 +11,5 @@
return this;
};
- $(document).on('ready load', function() {
- return $('body').renderGFM();
- });
+ $(() => $('body').renderGFM());
}).call(window);
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
index 703da749ad3..d6c864cb976 100644
--- a/app/assets/javascripts/repo/components/repo.vue
+++ b/app/assets/javascripts/repo/components/repo.vue
@@ -14,13 +14,13 @@ export default {
data: () => Store,
mixins: [RepoMixin],
components: {
- 'repo-sidebar': RepoSidebar,
- 'repo-tabs': RepoTabs,
- 'repo-file-buttons': RepoFileButtons,
+ RepoSidebar,
+ RepoTabs,
+ RepoFileButtons,
'repo-editor': MonacoLoaderHelper.repoEditorLoader,
- 'repo-commit-section': RepoCommitSection,
- 'popup-dialog': PopupDialog,
- 'repo-preview': RepoPreview,
+ RepoCommitSection,
+ PopupDialog,
+ RepoPreview,
},
mounted() {
@@ -28,12 +28,12 @@ export default {
},
methods: {
- dialogToggled(toggle) {
+ toggleDialogOpen(toggle) {
this.dialog.open = toggle;
},
dialogSubmitted(status) {
- this.dialog.open = false;
+ this.toggleDialogOpen(false);
this.dialog.status = status;
},
@@ -43,21 +43,28 @@ export default {
</script>
<template>
-<div class="repository-view tree-content-holder">
- <repo-sidebar/><div class="panel-right" :class="{'edit-mode': editMode}">
- <repo-tabs/>
- <component :is="currentBlobView" class="blob-viewer-container"></component>
- <repo-file-buttons/>
+ <div class="repository-view">
+ <div class="tree-content-holder" :class="{'tree-content-holder-mini' : isMini}">
+ <repo-sidebar/>
+ <div v-if="isMini"
+ class="panel-right"
+ :class="{'edit-mode': editMode}">
+ <repo-tabs/>
+ <component
+ :is="currentBlobView"
+ class="blob-viewer-container"/>
+ <repo-file-buttons/>
+ </div>
+ </div>
+ <repo-commit-section/>
+ <popup-dialog
+ v-show="dialog.open"
+ :primary-button-label="__('Discard changes')"
+ kind="warning"
+ :title="__('Are you sure?')"
+ :body="__('Are you sure you want to discard your changes?')"
+ @toggle="toggleDialogOpen"
+ @submit="dialogSubmitted"
+ />
</div>
- <repo-commit-section/>
- <popup-dialog
- :primary-button-label="__('Discard changes')"
- :open="dialog.open"
- kind="warning"
- :title="__('Are you sure?')"
- :body="__('Are you sure you want to discard your changes?')"
- @toggle="dialogToggled"
- @submit="dialogSubmitted"
- />
-</div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index bd83f80c928..5ec4a9b6593 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -2,18 +2,20 @@
/* global Flash */
import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin';
-import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
-const RepoCommitSection = {
+export default {
data: () => Store,
mixins: [RepoMixin],
computed: {
+ showCommitable() {
+ return this.isCommitable && this.changedFiles.length;
+ },
+
branchPaths() {
- const branch = Helper.getBranch();
- return this.changedFiles.map(f => Helper.getFilePathFromFullPath(f.url, branch));
+ return this.changedFiles.map(f => f.path);
},
cantCommitYet() {
@@ -28,11 +30,10 @@ const RepoCommitSection = {
methods: {
makeCommit() {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
- const branch = Helper.getBranch();
const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({
action: 'update',
- file_path: Helper.getFilePathFromFullPath(f.url, branch),
+ file_path: f.path,
content: f.newContent,
}));
const payload = {
@@ -47,51 +48,80 @@ const RepoCommitSection = {
resetCommitState() {
this.submitCommitsLoading = false;
this.changedFiles = [];
- this.openedFiles = [];
this.commitMessage = '';
this.editMode = false;
- $('html, body').animate({ scrollTop: 0 }, 'fast');
+ window.scrollTo(0, 0);
},
},
};
-
-export default RepoCommitSection;
</script>
<template>
-<div id="commit-area" v-if="isCommitable && changedFiles.length" >
- <form class="form-horizontal">
+<div
+ v-if="showCommitable"
+ id="commit-area">
+ <form
+ class="form-horizontal"
+ @submit.prevent="makeCommit">
<fieldset>
<div class="form-group">
- <label class="col-md-4 control-label staged-files">Staged files ({{changedFiles.length}})</label>
- <div class="col-md-4">
+ <label class="col-md-4 control-label staged-files">
+ Staged files ({{changedFiles.length}})
+ </label>
+ <div class="col-md-6">
<ul class="list-unstyled changed-files">
- <li v-for="file in branchPaths" :key="file.id">
- <span class="help-block">{{file}}</span>
+ <li
+ v-for="branchPath in branchPaths"
+ :key="branchPath">
+ <span class="help-block">
+ {{branchPath}}
+ </span>
</li>
</ul>
</div>
</div>
- <!-- Textarea
- -->
<div class="form-group">
- <label class="col-md-4 control-label" for="commit-message">Commit message</label>
- <div class="col-md-4">
- <textarea class="form-control" id="commit-message" name="commit-message" v-model="commitMessage"></textarea>
+ <label
+ class="col-md-4 control-label"
+ for="commit-message">
+ Commit message
+ </label>
+ <div class="col-md-6">
+ <textarea
+ id="commit-message"
+ class="form-control"
+ name="commit-message"
+ v-model="commitMessage">
+ </textarea>
</div>
</div>
- <!-- Button Drop Down
- -->
<div class="form-group target-branch">
- <label class="col-md-4 control-label" for="target-branch">Target branch</label>
- <div class="col-md-4">
- <span class="help-block">{{targetBranch}}</span>
+ <label
+ class="col-md-4 control-label"
+ for="target-branch">
+ Target branch
+ </label>
+ <div class="col-md-6">
+ <span class="help-block">
+ {{targetBranch}}
+ </span>
</div>
</div>
- <div class="col-md-offset-4 col-md-4">
- <button type="submit" :disabled="cantCommitYet" class="btn btn-success submit-commit" @click.prevent="makeCommit">
- <i class="fa fa-spinner fa-spin" v-if="submitCommitsLoading"></i>
- <span class="commit-summary">Commit {{changedFiles.length}} {{filePluralize}}</span>
+ <div class="col-md-offset-4 col-md-6">
+ <button
+ ref="submitCommit"
+ type="submit"
+ :disabled="cantCommitYet"
+ class="btn btn-success">
+ <i
+ v-if="submitCommitsLoading"
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ aria-label="loading">
+ </i>
+ <span class="commit-summary">
+ Commit {{changedFiles.length}} {{filePluralize}}
+ </span>
</button>
</div>
</fieldset>
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index f47b6c33fa2..29b76975561 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -10,12 +10,15 @@ export default {
return this.editMode ? this.__('Cancel edit') : this.__('Edit');
},
- buttonIcon() {
- return this.editMode ? [] : ['fa', 'fa-pencil'];
+ showButton() {
+ return this.isCommitable &&
+ !this.activeFile.render_error &&
+ !this.binary &&
+ this.openedFiles.length;
},
},
methods: {
- editClicked() {
+ editCancelClicked() {
if (this.changedFiles.length) {
this.dialog.open = true;
return;
@@ -23,25 +26,33 @@ export default {
this.editMode = !this.editMode;
Store.toggleBlobView();
},
+ toggleProjectRefsForm() {
+ $('.project-refs-form').toggleClass('disabled', this.editMode);
+ $('.js-tree-ref-target-holder').toggle(this.editMode);
+ },
},
watch: {
editMode() {
- if (this.editMode) {
- $('.project-refs-form').addClass('disabled');
- $('.js-tree-ref-target-holder').show();
- } else {
- $('.project-refs-form').removeClass('disabled');
- $('.js-tree-ref-target-holder').hide();
- }
+ this.toggleProjectRefsForm();
},
},
};
</script>
<template>
-<button class="btn btn-default" @click.prevent="editClicked" v-cloak v-if="isCommitable && !activeFile.render_error" :disabled="binary">
- <i :class="buttonIcon"></i>
- <span>{{buttonLabel}}</span>
+<button
+ v-if="showButton"
+ class="btn btn-default"
+ type="button"
+ @click.prevent="editCancelClicked">
+ <i
+ v-if="!editMode"
+ class="fa fa-pencil"
+ aria-hidden="true">
+ </i>
+ <span>
+ {{buttonLabel}}
+ </span>
</button>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
index fd1a21e15b4..96d6a75bb61 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -8,38 +8,39 @@ const RepoEditor = {
data: () => Store,
destroyed() {
- // this.monacoInstance.getModels().forEach((m) => {
- // m.dispose();
- // });
- this.monacoInstance.destroy();
+ if (Helper.monacoInstance) {
+ Helper.monacoInstance.destroy();
+ }
},
mounted() {
Service.getRaw(this.activeFile.raw_path)
- .then((rawResponse) => {
- Store.blobRaw = rawResponse.data;
- Helper.findOpenedFileFromActive().plain = rawResponse.data;
+ .then((rawResponse) => {
+ Store.blobRaw = rawResponse.data;
+ Store.activeFile.plain = rawResponse.data;
- const monacoInstance = this.monaco.editor.create(this.$el, {
- model: null,
- readOnly: false,
- contextmenu: false,
- });
+ const monacoInstance = Helper.monaco.editor.create(this.$el, {
+ model: null,
+ readOnly: false,
+ contextmenu: false,
+ });
- Store.monacoInstance = monacoInstance;
+ Helper.monacoInstance = monacoInstance;
- this.addMonacoEvents();
+ this.addMonacoEvents();
- const languages = this.monaco.languages.getLanguages();
- const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
- this.showHide();
- const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
-
- this.monacoInstance.setModel(newModel);
- }).catch(Helper.loadingError);
+ this.setupEditor();
+ })
+ .catch(Helper.loadingError);
},
methods: {
+ setupEditor() {
+ this.showHide();
+
+ Helper.setMonacoModelFromLanguage();
+ },
+
showHide() {
if (!this.openedFiles.length || (this.binary && !this.activeFile.raw)) {
this.$el.style.display = 'none';
@@ -49,41 +50,36 @@ const RepoEditor = {
},
addMonacoEvents() {
- this.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp);
- this.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
+ Helper.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp);
+ Helper.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
},
onMonacoEditorKeysPressed() {
- Store.setActiveFileContents(this.monacoInstance.getValue());
+ Store.setActiveFileContents(Helper.monacoInstance.getValue());
},
onMonacoEditorMouseUp(e) {
+ if (!e.target.position) return;
const lineNumber = e.target.position.lineNumber;
- if (e.target.element.className === 'line-numbers') {
+ if (e.target.element.classList.contains('line-numbers')) {
location.hash = `L${lineNumber}`;
Store.activeLine = lineNumber;
+
+ Helper.monacoInstance.setPosition({
+ lineNumber: this.activeLine,
+ column: 1,
+ });
}
},
},
watch: {
- activeLine() {
- this.monacoInstance.setPosition({
- lineNumber: this.activeLine,
- column: 1,
- });
- },
-
- activeFileLabel() {
- this.showHide();
- },
-
dialog: {
handler(obj) {
const newObj = obj;
if (newObj.status) {
newObj.status = false;
- this.openedFiles.map((file) => {
+ this.openedFiles = this.openedFiles.map((file) => {
const f = file;
if (f.active) {
this.blobRaw = f.plain;
@@ -94,35 +90,21 @@ const RepoEditor = {
return f;
});
this.editMode = false;
+ Store.toggleBlobView();
}
},
deep: true,
},
- isTree() {
- this.showHide();
- },
-
- openedFiles() {
- this.showHide();
- },
-
- binary() {
- this.showHide();
- },
-
blobRaw() {
- this.showHide();
-
- if (this.isTree) return;
-
- this.monacoInstance.setModel(null);
-
- const languages = this.monaco.languages.getLanguages();
- const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
- const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
-
- this.monacoInstance.setModel(newModel);
+ if (Helper.monacoInstance && !this.isTree) {
+ this.setupEditor();
+ }
+ },
+ },
+ computed: {
+ shouldHideEditor() {
+ return !this.openedFiles.length || (this.binary && !this.activeFile.raw);
},
},
};
@@ -131,5 +113,5 @@ export default RepoEditor;
</script>
<template>
-<div id="ide"></div>
+<div id="ide" v-if='!shouldHideEditor'></div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue
index f604bc22a26..20ebf840774 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/repo/components/repo_file.vue
@@ -33,6 +33,26 @@ const RepoFile = {
canShowFile() {
return !this.loading.tree || this.hasFiles;
},
+
+ fileIcon() {
+ const classObj = {
+ 'fa-spinner fa-spin': this.file.loading,
+ [this.file.icon]: !this.file.loading,
+ };
+ return classObj;
+ },
+
+ fileIndentation() {
+ return {
+ 'margin-left': `${this.file.level * 10}px`,
+ };
+ },
+
+ activeFileClass() {
+ return {
+ active: this.activeFile.url === this.file.url,
+ };
+ },
},
methods: {
@@ -46,21 +66,42 @@ export default RepoFile;
</script>
<template>
-<tr class="file" v-if="canShowFile" :class="{'active': activeFile.url === file.url}">
- <td @click.prevent="linkClicked(file)">
- <i class="fa file-icon" v-if="!file.loading" :class="file.icon" :style="{'margin-left': file.level * 10 + 'px'}"></i>
- <i class="fa fa-spinner fa-spin" v-if="file.loading" :style="{'margin-left': file.level * 10 + 'px'}"></i>
- <a :href="file.url" class="repo-file-name" :title="file.url">{{file.name}}</a>
+<tr
+ v-if="canShowFile"
+ class="file"
+ :class="activeFileClass"
+ @click.prevent="linkClicked(file)">
+ <td>
+ <i
+ class="fa fa-fw file-icon"
+ :class="fileIcon"
+ :style="fileIndentation"
+ aria-label="file icon">
+ </i>
+ <a
+ :href="file.url"
+ class="repo-file-name"
+ :title="file.url">
+ {{file.name}}
+ </a>
</td>
- <td v-if="!isMini" class="hidden-sm hidden-xs">
- <div class="commit-message">
- <a :href="file.lastCommitUrl">{{file.lastCommitMessage}}</a>
- </div>
- </td>
+ <template v-if="!isMini">
+ <td class="hidden-sm hidden-xs">
+ <div class="commit-message">
+ <a @click.stop :href="file.lastCommitUrl">
+ {{file.lastCommitMessage}}
+ </a>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-xs">
- <span class="commit-update" :title="tooltipTitle(file.lastCommitUpdate)">{{timeFormated(file.lastCommitUpdate)}}</span>
- </td>
+ <td class="hidden-xs">
+ <span
+ class="commit-update"
+ :title="tooltipTitle(file.lastCommitUpdate)">
+ {{timeFormated(file.lastCommitUpdate)}}
+ </span>
+ </td>
+ </template>
</tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue
index 628d02ca704..e43ef366f47 100644
--- a/app/assets/javascripts/repo/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/repo/components/repo_file_buttons.vue
@@ -15,7 +15,7 @@ const RepoFileButtons = {
},
canPreview() {
- return Helper.isKindaBinary();
+ return Helper.isRenderable();
},
},
@@ -28,15 +28,42 @@ export default RepoFileButtons;
</script>
<template>
-<div id="repo-file-buttons" v-if="isMini">
- <a :href="activeFile.raw_path" target="_blank" class="btn btn-default raw" rel="noopener noreferrer">{{rawDownloadButtonLabel}}</a>
+ <div id="repo-file-buttons">
+ <a
+ :href="activeFile.raw_path"
+ target="_blank"
+ class="btn btn-default raw"
+ rel="noopener noreferrer">
+ {{rawDownloadButtonLabel}}
+ </a>
- <div class="btn-group" role="group" aria-label="File actions">
- <a :href="activeFile.blame_path" class="btn btn-default blame">Blame</a>
- <a :href="activeFile.commits_path" class="btn btn-default history">History</a>
- <a :href="activeFile.permalink" class="btn btn-default permalink">Permalink</a>
- </div>
+ <div
+ class="btn-group"
+ role="group"
+ aria-label="File actions">
+ <a
+ :href="activeFile.blame_path"
+ class="btn btn-default blame">
+ Blame
+ </a>
+ <a
+ :href="activeFile.commits_path"
+ class="btn btn-default history">
+ History
+ </a>
+ <a
+ :href="activeFile.permalink"
+ class="btn btn-default permalink">
+ Permalink
+ </a>
+ </div>
- <a href="#" v-if="canPreview" @click.prevent="rawPreviewToggle" class="btn btn-default preview">{{activeFileLabel}}</a>
-</div>
+ <a
+ v-if="canPreview"
+ href="#"
+ @click.prevent="rawPreviewToggle"
+ class="btn btn-default preview">
+ {{activeFileLabel}}
+ </a>
+ </div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue
index ba53ce0eecc..6a15755f029 100644
--- a/app/assets/javascripts/repo/components/repo_file_options.vue
+++ b/app/assets/javascripts/repo/components/repo_file_options.vue
@@ -17,7 +17,7 @@ export default RepoFileOptions;
</script>
<template>
-<tr v-if="isMini" class="repo-file-options">
+ <tr v-if="isMini" class="repo-file-options">
<td>
<span class="title">{{projectName}}</span>
</td>
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue
index 38e9f16d041..bc8c64c8362 100644
--- a/app/assets/javascripts/repo/components/repo_loading_file.vue
+++ b/app/assets/javascripts/repo/components/repo_loading_file.vue
@@ -18,9 +18,15 @@ const RepoLoadingFile = {
},
},
+ computed: {
+ showGhostLines() {
+ return this.loading.tree && !this.hasFiles;
+ },
+ },
+
methods: {
lineOfCode(n) {
- return `line-of-code-${n}`;
+ return `skeleton-line-${n}`;
},
},
};
@@ -29,23 +35,42 @@ export default RepoLoadingFile;
</script>
<template>
-<tr v-if="loading.tree && !hasFiles" class="loading-file">
- <td>
- <div class="animation-container animation-container-small">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
+ <tr
+ v-if="showGhostLines"
+ class="loading-file">
+ <td>
+ <div
+ class="animation-container animation-container-small">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-sm hidden-xs">
- <div class="animation-container">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
+ <td
+ v-if="!isMini"
+ class="hidden-sm hidden-xs">
+ <div class="animation-container">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-xs">
- <div class="animation-container animation-container-small">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
-</tr>
+ <td
+ v-if="!isMini"
+ class="hidden-xs">
+ <div class="animation-container animation-container-small">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
+ </tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue
index 6a0d684052f..bbdbdc61e38 100644
--- a/app/assets/javascripts/repo/components/repo_prev_directory.vue
+++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue
@@ -1,4 +1,6 @@
<script>
+import RepoMixin from '../mixins/repo_mixin';
+
const RepoPreviousDirectory = {
props: {
prevUrl: {
@@ -7,6 +9,14 @@ const RepoPreviousDirectory = {
},
},
+ mixins: [RepoMixin],
+
+ computed: {
+ colSpanCondition() {
+ return this.isMini ? undefined : 3;
+ },
+ },
+
methods: {
linkClicked(file) {
this.$emit('linkclicked', file);
@@ -19,8 +29,10 @@ export default RepoPreviousDirectory;
<template>
<tr class="prev-directory">
- <td colspan="3">
- <a :href="prevUrl" @click.prevent="linkClicked(prevUrl)">..</a>
+ <td
+ :colspan="colSpanCondition"
+ @click.prevent="linkClicked(prevUrl)">
+ <a :href="prevUrl">..</a>
</td>
</tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
index 0d4f8c6635e..72b40288566 100644
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ b/app/assets/javascripts/repo/components/repo_sidebar.vue
@@ -8,7 +8,7 @@ import RepoFile from './repo_file.vue';
import RepoLoadingFile from './repo_loading_file.vue';
import RepoMixin from '../mixins/repo_mixin';
-const RepoSidebar = {
+export default {
mixins: [RepoMixin],
components: {
'repo-file-options': RepoFileOptions,
@@ -35,7 +35,7 @@ const RepoSidebar = {
fileClicked(clickedFile) {
let file = clickedFile;
-
+ if (file.loading) return;
file.loading = true;
if (file.type === 'tree' && file.opened) {
file = Store.removeChildFilesOfTree(file);
@@ -59,12 +59,10 @@ const RepoSidebar = {
},
},
};
-
-export default RepoSidebar;
</script>
<template>
-<div id="sidebar" :class="{'sidebar-mini' : isMini}" v-cloak>
+<div id="sidebar" :class="{'sidebar-mini' : isMini}">
<table class="table">
<thead v-if="!isMini">
<tr>
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue
index fc66a8ea953..0d0c34ec741 100644
--- a/app/assets/javascripts/repo/components/repo_tab.vue
+++ b/app/assets/javascripts/repo/components/repo_tab.vue
@@ -18,8 +18,8 @@ const RepoTab = {
},
changedClass() {
const tabChangedObj = {
- 'fa-times': !this.tab.changed,
- 'fa-circle': this.tab.changed,
+ 'fa-times close-icon': !this.tab.changed,
+ 'fa-circle unsaved-icon': this.tab.changed,
};
return tabChangedObj;
},
@@ -28,9 +28,9 @@ const RepoTab = {
methods: {
tabClicked: Store.setActiveFiles,
- xClicked(file) {
+ closeTab(file) {
if (file.changed) return;
- this.$emit('xclicked', file);
+ this.$emit('tabclosed', file);
},
},
};
@@ -39,11 +39,11 @@ export default RepoTab;
</script>
<template>
-<li>
+<li @click="tabClicked(tab)">
<a
href="#0"
class="close"
- @click.prevent="xClicked(tab)"
+ @click.stop.prevent="closeTab(tab)"
:aria-label="closeLabel">
<i
class="fa"
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue
index bbd60d9d793..9c5bfc5d0cf 100644
--- a/app/assets/javascripts/repo/components/repo_tabs.vue
+++ b/app/assets/javascripts/repo/components/repo_tabs.vue
@@ -13,7 +13,7 @@ const RepoTabs = {
data: () => Store,
methods: {
- xClicked(file) {
+ tabClosed(file) {
Store.removeFromOpenedFiles(file);
},
},
@@ -23,10 +23,14 @@ export default RepoTabs;
</script>
<template>
-<ul
- v-if="isMini"
- id="tabs">
- <repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
+<ul id="tabs">
+ <repo-tab
+ v-for="tab in openedFiles"
+ :key="tab.id"
+ :tab="tab"
+ :class="{'active' : tab.active}"
+ @tabclosed="tabClosed"
+ />
<li class="tabs-divider" />
</ul>
</template>
diff --git a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
index c1a0e80f8f3..f8729bbf585 100644
--- a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
+++ b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
@@ -1,13 +1,14 @@
/* global monaco */
import RepoEditor from '../components/repo_editor.vue';
import Store from '../stores/repo_store';
+import Helper from '../helpers/repo_helper';
import monacoLoader from '../monaco_loader';
function repoEditorLoader() {
Store.monacoLoading = true;
return new Promise((resolve, reject) => {
monacoLoader(['vs/editor/editor.main'], () => {
- Store.monaco = monaco;
+ Helper.monaco = monaco;
Store.monacoLoading = false;
resolve(RepoEditor);
}, () => {
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
index 17aaa0e1584..2bd8d7eea65 100644
--- a/app/assets/javascripts/repo/helpers/repo_helper.js
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -4,6 +4,8 @@ import Store from '../stores/repo_store';
import '../../flash';
const RepoHelper = {
+ monacoInstance: null,
+
getDefaultActiveFile() {
return {
active: true,
@@ -37,10 +39,6 @@ const RepoHelper = {
return fileName.split('.').pop();
},
- getBranch() {
- return $('button.dropdown-menu-toggle').attr('data-ref');
- },
-
getLanguageIDForFile(file, langs) {
const ext = RepoHelper.getFileExtension(file.name);
const foundLang = RepoHelper.findLanguage(ext, langs);
@@ -48,8 +46,12 @@ const RepoHelper = {
return foundLang ? foundLang.id : 'plaintext';
},
- getFilePathFromFullPath(fullPath, branch) {
- return fullPath.split(`${Store.projectUrl}/blob/${branch}`)[1];
+ setMonacoModelFromLanguage() {
+ RepoHelper.monacoInstance.setModel(null);
+ const languages = RepoHelper.monaco.languages.getLanguages();
+ const languageID = RepoHelper.getLanguageIDForFile(Store.activeFile, languages);
+ const newModel = RepoHelper.monaco.editor.createModel(Store.blobRaw, languageID);
+ RepoHelper.monacoInstance.setModel(newModel);
},
findLanguage(ext, langs) {
@@ -62,11 +64,11 @@ const RepoHelper = {
file.opened = true;
file.icon = 'fa-folder-open';
- RepoHelper.toURL(file.url, file.name);
+ RepoHelper.updateHistoryEntry(file.url, file.name);
return file;
},
- isKindaBinary() {
+ isRenderable() {
const okExts = ['md', 'svg'];
return okExts.indexOf(Store.activeFile.extension) > -1;
},
@@ -80,22 +82,8 @@ const RepoHelper = {
.catch(RepoHelper.loadingError);
},
- toggleFakeTab(loading, file) {
- if (loading) return Store.addPlaceholderFile();
- return Store.removeFromOpenedFiles(file);
- },
-
- setLoading(loading, file) {
- if (Service.url.indexOf('blob') > -1) {
- Store.loading.blob = loading;
- return RepoHelper.toggleFakeTab(loading, file);
- }
-
- if (Service.url.indexOf('tree') > -1) Store.loading.tree = loading;
-
- return undefined;
- },
-
+ // when you open a directory you need to put the directory files under
+ // the directory... This will merge the list of the current directory and the new list.
getNewMergedList(inDirectory, currentList, newList) {
const newListSorted = newList.sort(this.compareFilesCaseInsensitive);
if (!inDirectory) return newListSorted;
@@ -104,6 +92,9 @@ const RepoHelper = {
return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile);
},
+ // within the get new merged list this does the merging of the current list of files
+ // and the new list of files. The files are never "in" another directory they just
+ // appear like they are because of the margin.
mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) {
newList.reverse().forEach((newFile) => {
const fileIndex = indexOfFile + 1;
@@ -141,11 +132,9 @@ const RepoHelper = {
getContent(treeOrFile) {
let file = treeOrFile;
- // const loadingData = RepoHelper.setLoading(true);
return Service.getContent()
.then((response) => {
const data = response.data;
- // RepoHelper.setLoading(false, loadingData);
Store.isTree = RepoHelper.isTree(data);
if (!Store.isTree) {
if (!file) file = data;
@@ -246,37 +235,19 @@ const RepoHelper = {
},
dataToListOfFiles(data) {
- const a = [];
-
- // push in blobs
- data.blobs.forEach((blob) => {
- a.push(RepoHelper.serializeBlob(blob));
- });
-
- data.trees.forEach((tree) => {
- a.push(RepoHelper.serializeTree(tree));
- });
-
- data.submodules.forEach((submodule) => {
- a.push(RepoHelper.serializeSubmodule(submodule));
- });
-
- return a;
+ const { blobs, trees, submodules } = data;
+ return [
+ ...blobs.map(blob => RepoHelper.serializeBlob(blob)),
+ ...trees.map(tree => RepoHelper.serializeTree(tree)),
+ ...submodules.map(submodule => RepoHelper.serializeSubmodule(submodule)),
+ ];
},
genKey() {
return RepoHelper.Time.now().toFixed(3);
},
- getStateKey() {
- return RepoHelper.key;
- },
-
- setStateKey(key) {
- RepoHelper.key = key;
- },
-
- toURL(url, title) {
+ updateHistoryEntry(url, title) {
const history = window.history;
RepoHelper.key = RepoHelper.genKey();
@@ -293,7 +264,7 @@ const RepoHelper = {
},
loadingError() {
- Flash('Unable to load the file at this time.');
+ Flash('Unable to load this content at this time.');
},
};
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
index 3e37da1726e..6c1d468e937 100644
--- a/app/assets/javascripts/repo/index.js
+++ b/app/assets/javascripts/repo/index.js
@@ -33,6 +33,8 @@ function setInitialStore(data) {
Store.projectId = data.projectId;
Store.projectName = data.projectName;
Store.projectUrl = data.projectUrl;
+ Store.canCommit = data.canCommit;
+ Store.onTopOfBranch = data.onTopOfBranch;
Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref');
Store.checkIsCommitable();
}
@@ -43,6 +45,9 @@ function initRepo(el) {
components: {
repo: Repo,
},
+ render(createElement) {
+ return createElement('repo');
+ },
});
}
diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js
index 17578f3bbf3..3cf204e6ec8 100644
--- a/app/assets/javascripts/repo/services/repo_service.js
+++ b/app/assets/javascripts/repo/services/repo_service.js
@@ -13,14 +13,6 @@ const RepoService = {
},
richExtensionRegExp: /md/,
- checkCurrentBranchIsCommitable() {
- const url = Store.service.refsUrl;
- return axios.get(url, { params: {
- ref: Store.currentBranch,
- search: Store.currentBranch,
- } });
- },
-
getRaw(url) {
return axios.get(url, {
// Stop Axios from parsing a JSON file into a JS object
@@ -75,7 +67,11 @@ const RepoService = {
commitFiles(payload, cb) {
Api.commitMultiple(Store.projectId, payload, (data) => {
- Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
+ if (data.short_id && data.stats) {
+ Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
+ } else {
+ Flash(data.message);
+ }
cb();
});
},
diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js
index bb605540aad..1c0df528aea 100644
--- a/app/assets/javascripts/repo/stores/repo_store.js
+++ b/app/assets/javascripts/repo/stores/repo_store.js
@@ -5,8 +5,9 @@ import Service from '../services/repo_service';
const RepoStore = {
monaco: {},
monacoLoading: false,
- monacoInstance: {},
service: '',
+ canCommit: false,
+ onTopOfBranch: false,
editMode: false,
isTree: false,
isRoot: false,
@@ -52,14 +53,7 @@ const RepoStore = {
// mutations
checkIsCommitable() {
- RepoStore.service.checkCurrentBranchIsCommitable()
- .then((data) => {
- // you shouldn't be able to make commits on commits or tags.
- const { Branches, Commits, Tags } = data.data;
- if (Branches && Branches.length) RepoStore.isCommitable = true;
- if (Commits && Commits.length) RepoStore.isCommitable = false;
- if (Tags && Tags.length) RepoStore.isCommitable = false;
- }).catch(() => Flash('Failed to check if branch can be committed to.'));
+ RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit;
},
addFilesToDirectory(inDirectory, currentList, newList) {
@@ -90,7 +84,7 @@ const RepoStore = {
}).catch(Helper.loadingError);
}
- if (!file.loading) Helper.toURL(file.url, file.name);
+ if (!file.loading) Helper.updateHistoryEntry(file.url, file.name);
RepoStore.binary = file.binary;
},
@@ -117,15 +111,15 @@ const RepoStore = {
removeChildFilesOfTree(tree) {
let foundTree = false;
const treeToClose = tree;
- let wereDone = false;
+ let canStopSearching = false;
RepoStore.files = RepoStore.files.filter((file) => {
const isItTheTreeWeWant = file.url === treeToClose.url;
// if it's the next tree
if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) {
- wereDone = true;
+ canStopSearching = true;
return true;
}
- if (wereDone) return true;
+ if (canStopSearching) return true;
if (isItTheTreeWeWant) foundTree = true;
@@ -142,8 +136,8 @@ const RepoStore = {
if (file.type === 'tree') return;
let foundIndex;
RepoStore.openedFiles = RepoStore.openedFiles.filter((openedFile, i) => {
- if (openedFile.url === file.url) foundIndex = i;
- return openedFile.url !== file.url;
+ if (openedFile.path === file.path) foundIndex = i;
+ return openedFile.path !== file.path;
});
// now activate the right tab based on what you closed.
@@ -157,36 +151,16 @@ const RepoStore = {
return;
}
- if (foundIndex) {
- if (foundIndex > 0) {
- RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]);
- }
+ if (foundIndex && foundIndex > 0) {
+ RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]);
}
},
- addPlaceholderFile() {
- const randomURL = Helper.Time.now();
- const newFakeFile = {
- active: false,
- binary: true,
- type: 'blob',
- loading: true,
- mime_type: 'loading',
- name: 'loading',
- url: randomURL,
- fake: true,
- };
-
- RepoStore.openedFiles.push(newFakeFile);
-
- return newFakeFile;
- },
-
addToOpenedFiles(file) {
const openFile = file;
const openedFilesAlreadyExists = RepoStore.openedFiles
- .some(openedFile => openedFile.url === openFile.url);
+ .some(openedFile => openedFile.path === openFile.path);
if (openedFilesAlreadyExists) return;
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 422c02c7b7e..8e7abdbffef 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -71,7 +71,7 @@ export default {
/>
<div v-if="!isConfidential" class="no-value confidential-value">
<i class="fa fa-eye is-not-confidential"></i>
- None
+ Not confidential
</div>
<div v-else class="value confidential-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
index 7d339c0e753..994b33bc1c9 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -1,31 +1,37 @@
<script>
-const PopupDialog = {
+export default {
name: 'popup-dialog',
props: {
- open: Boolean,
- title: String,
- body: String,
+ title: {
+ type: String,
+ required: true,
+ },
+ body: {
+ type: String,
+ required: true,
+ },
kind: {
type: String,
+ required: false,
default: 'primary',
},
closeButtonLabel: {
type: String,
+ required: false,
default: 'Cancel',
},
primaryButtonLabel: {
type: String,
- default: 'Save changes',
+ required: true,
},
},
computed: {
- typeOfClass() {
- const className = `btn-${this.kind}`;
- const returnObj = {};
- returnObj[className] = true;
- return returnObj;
+ btnKindClass() {
+ return {
+ [`btn-${this.kind}`]: true,
+ };
},
},
@@ -33,33 +39,45 @@ const PopupDialog = {
close() {
this.$emit('toggle', false);
},
-
- yesClick() {
- this.$emit('submit', true);
- },
-
- noClick() {
- this.$emit('submit', false);
+ emitSubmit(status) {
+ this.$emit('submit', status);
},
},
};
-
-export default PopupDialog;
</script>
+
<template>
-<div class="modal popup-dialog" tabindex="-1" v-show="open" role="dialog">
+<div
+ class="modal popup-dialog"
+ role="dialog"
+ tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <button type="button" class="close" @click="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <button type="button"
+ class="close"
+ @click="close"
+ aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
<h4 class="modal-title">{{this.title}}</h4>
</div>
<div class="modal-body">
<p>{{this.body}}</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal" @click="noClick">{{closeButtonLabel}}</button>
- <button type="button" class="btn" :class="typeOfClass" @click="yesClick">{{primaryButtonLabel}}</button>
+ <button
+ type="button"
+ class="btn btn-default"
+ @click="emitSubmit(false)">
+ {{closeButtonLabel}}
+ </button>
+ <button type="button"
+ class="btn"
+ :class="btnKindClass"
+ @click="emitSubmit(true)">
+ {{primaryButtonLabel}}
+ </button>
</div>
</div>
</div>
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 3cd7f81da47..667b73e150d 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -187,3 +187,81 @@ a {
.fade-in-full {
animation: fadeInFull $fade-in-duration 1;
}
+
+
+.animation-container {
+ background: $repo-editor-grey;
+ height: 40px;
+ overflow: hidden;
+ position: relative;
+
+ &.animation-container-small {
+ height: 12px;
+ }
+
+ &::before {
+ animation-duration: 1s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-name: blockTextShine;
+ animation-timing-function: linear;
+ background-image: $repo-editor-linear-gradient;
+ background-repeat: no-repeat;
+ background-size: 800px 45px;
+ content: ' ';
+ display: block;
+ height: 100%;
+ position: relative;
+ }
+
+ div {
+ background: $white-light;
+ height: 6px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+
+ .skeleton-line-1 {
+ left: 0;
+ top: 8px;
+ }
+
+ .skeleton-line-2 {
+ left: 150px;
+ top: 0;
+ height: 10px;
+ }
+
+ .skeleton-line-3 {
+ left: 0;
+ top: 23px;
+ }
+
+ .skeleton-line-4 {
+ left: 0;
+ top: 38px;
+ }
+
+ .skeleton-line-5 {
+ left: 200px;
+ top: 28px;
+ height: 10px;
+ }
+
+ .skeleton-line-6 {
+ top: 14px;
+ left: 230px;
+ height: 10px;
+ }
+}
+
+@keyframes blockTextShine {
+ 0% {
+ transform: translateX(-468px);
+ }
+
+ 100% {
+ transform: translateX(468px);
+ }
+}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index bd0367f86dd..bd521028c44 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -117,10 +117,6 @@ body {
margin-top: $header-height + $performance-bar-height;
}
-[v-cloak] {
- display: none;
-}
-
.vertical-center {
min-height: 100vh;
display: flex;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index fcd4c72b430..e3920b5d3d9 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -204,6 +204,16 @@
}
}
+ div.avatar {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ .center {
+ line-height: 14px;
+ }
+ }
+
strong {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index d386ac5ba9c..071f20fc457 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -161,6 +161,8 @@
}
.nav-controls {
+ @include new-style-dropdown;
+
display: inline-block;
float: right;
text-align: right;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 795ee91af8b..3e2f23e6b2a 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -403,6 +403,7 @@ header.navbar-gitlab-new {
}
.breadcrumbs-extra {
+ display: flex;
flex: 0 0 auto;
margin-left: auto;
}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index faedd207e01..d078c8b956b 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -97,9 +97,9 @@ $new-sidebar-collapsed-width: 50px;
top: $header-height;
bottom: 0;
left: 0;
- overflow: auto;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
+ transform: translate3d(0, 0, 0);
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
@@ -176,6 +176,12 @@ $new-sidebar-collapsed-width: 50px;
}
}
+.nav-sidebar-inner-scroll {
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+}
+
.with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 215bedc04fd..913a1a95dca 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -560,9 +560,13 @@
}
.diff-files-changed {
+ .inline-parallel-buttons {
+ position: relative;
+ z-index: 1;
+ }
+
.commit-stat-summary {
@include new-style-dropdown;
- z-index: -1;
@media (min-width: $screen-sm-min) {
margin-left: -$gl-padding;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index d14b976374c..1dac38f2b6d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -8,13 +8,13 @@
.is-confidential {
color: $orange-600;
background-color: $orange-50;
- border-radius: 3px;
+ border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
}
.is-not-confidential {
- border-radius: 3px;
+ border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
}
@@ -81,6 +81,7 @@
border: 1px solid $white-normal;
padding: 5px;
max-height: calc(100vh - 100px);
+ max-width: 100%;
}
.emoji-block {
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index ad17078c98a..1f4d4698199 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -1,6 +1,6 @@
.fade-enter-active,
.fade-leave-active {
- transition: opacity .5s;
+ transition: opacity $sidebar-transition-duration;
}
.monaco-loader {
@@ -28,11 +28,6 @@
.project-refs-form,
.project-refs-target-form {
display: inline-block;
-
- &.disabled {
- opacity: 0.5;
- pointer-events: none;
- }
}
.fade-enter,
@@ -52,14 +47,26 @@
margin: 20px;
}
-.repository-view.tree-content-holder {
+.repository-view {
border: 1px solid $border-color;
border-radius: $border-radius-default;
color: $almost-black;
+ .tree-content-holder {
+ display: flex;
+ max-height: 100vh;
+ min-height: 300px;
+ }
+
+ .tree-content-holder-mini {
+ height: 100vh;
+ }
+
.panel-right {
- display: inline-block;
+ display: flex;
+ flex-direction: column;
width: 80%;
+ height: 100%;
.monaco-editor.vs {
.line-numbers {
@@ -90,16 +97,17 @@
}
.blob-viewer-container {
- height: calc(100vh - 63px);
+ flex: 1;
overflow: auto;
}
#tabs {
+ flex-shrink: 0;
+ display: flex;
+ width: 100%;
padding-left: 0;
margin-bottom: 0;
- display: flex;
white-space: nowrap;
- width: 100%;
overflow-y: hidden;
overflow-x: auto;
@@ -114,6 +122,7 @@
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
white-space: nowrap;
+ cursor: pointer;
&.remove {
animation: swipeRightDissapear ease-in 0.1s;
@@ -133,10 +142,10 @@
a {
@include str-truncated(100px);
color: $black;
- display: inline-block;
width: 100px;
text-align: center;
vertical-align: middle;
+ text-decoration: none;
&.close {
width: auto;
@@ -146,15 +155,15 @@
}
}
- i.fa.fa-times,
- i.fa.fa-circle {
+ .close-icon,
+ .unsaved-icon {
float: right;
margin-top: 3px;
margin-left: 15px;
color: $gray-darkest;
}
- i.fa.fa-circle {
+ .unsaved-icon {
color: $brand-success;
}
@@ -204,7 +213,7 @@
background: $gray-light;
padding: 20px;
- span.help-block {
+ .help-block {
padding-top: 7px;
margin-top: 0;
}
@@ -226,13 +235,12 @@
}
#sidebar {
+ flex: 1;
+ height: 100%;
&.sidebar-mini {
- display: inline-block;
- vertical-align: top;
width: 20%;
border-right: 1px solid $white-normal;
- height: calc(100vh + 20px);
overflow: auto;
}
@@ -261,7 +269,6 @@
text-transform: uppercase;
font-weight: bold;
color: $gray-darkest;
- width: 185px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -270,7 +277,7 @@
}
}
- .fa {
+ .file-icon {
margin-right: 5px;
}
@@ -280,118 +287,22 @@
}
a {
+ @include str-truncated(250px);
color: $almost-black;
display: inline-block;
vertical-align: middle;
}
-
- ul {
- list-style-type: none;
- padding: 0;
-
- li {
- border-bottom: 1px solid $border-gray-normal;
- padding: 10px 20px;
-
- a {
- color: $almost-black;
- }
-
- .fa {
- font-size: $code_font_size;
- margin-right: 5px;
- }
- }
- }
- }
-
-}
-
-.animation-container {
- background: $repo-editor-grey;
- height: 40px;
- overflow: hidden;
- position: relative;
-
- &.animation-container-small {
- height: 12px;
- }
-
- &::before {
- animation-duration: 1s;
- animation-fill-mode: forwards;
- animation-iteration-count: infinite;
- animation-name: blockTextShine;
- animation-timing-function: linear;
- background-image: $repo-editor-linear-gradient;
- background-repeat: no-repeat;
- background-size: 800px 45px;
- content: ' ';
- display: block;
- height: 100%;
- position: relative;
- }
-
- div {
- background: $white-light;
- height: 6px;
- left: 0;
- position: absolute;
- right: 0;
- }
-
- .line-of-code-1 {
- left: 0;
- top: 8px;
- }
-
- .line-of-code-2 {
- left: 150px;
- top: 0;
- height: 10px;
- }
-
- .line-of-code-3 {
- left: 0;
- top: 23px;
- }
-
- .line-of-code-4 {
- left: 0;
- top: 38px;
- }
-
- .line-of-code-5 {
- left: 200px;
- top: 28px;
- height: 10px;
- }
-
- .line-of-code-6 {
- top: 14px;
- left: 230px;
- height: 10px;
}
}
.render-error {
- min-height: calc(100vh - 63px);
+ min-height: calc(100vh - 62px);
p {
width: 100%;
}
}
-@keyframes blockTextShine {
- 0% {
- transform: translateX(-468px);
- }
-
- 100% {
- transform: translateX(468px);
- }
-}
-
@keyframes swipeRightAppear {
0% {
transform: scaleX(0.00);
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 0c3b68a7ac3..4079072a930 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -10,7 +10,7 @@ module IssuableActions
def destroy
issuable.destroy
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
- TodoService.new.public_send(destroy_method, issuable, current_user)
+ TodoService.new.public_send(destroy_method, issuable, current_user) # rubocop:disable GitlabSecurity/PublicSend
name = issuable.human_class_name
flash[:notice] = "The #{name} was successfully deleted."
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index c0ac47e363d..96ce686c989 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -34,7 +34,7 @@ class Groups::ApplicationController < ApplicationController
def build_canonical_path(group)
params[:group_id] = group.to_param
-
+
url_for(params)
end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index baa6645e5ce..ab18d86dcae 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -64,7 +64,7 @@ class Import::GithubController < Import::BaseController
end
def import_enabled?
- __send__("#{provider}_import_enabled?")
+ __send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
end
def new_import_url
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index b4213574561..7444826a5d1 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -142,13 +142,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
-
+
def fail_login
error_message = @user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
-
+
def fail_ldap_login
flash[:alert] = 'Access denied for your LDAP account.'
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index a2e8c10857d..2b8f3977e6e 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -198,6 +198,10 @@ class Projects::BlobController < Projects::ApplicationController
json = blob_json(@blob)
return render_404 unless json
+ path_segments = @path.split('/')
+ path_segments.pop
+ tree_path = path_segments.join('/')
+
render json: json.merge(
path: blob.path,
name: blob.name,
@@ -212,6 +216,7 @@ class Projects::BlobController < Projects::ApplicationController
raw_path: project_raw_path(project, @id),
blame_path: project_blame_path(project, @id),
commits_path: project_commits_path(project, @id),
+ tree_path: project_tree_path(project, File.join(@ref, tree_path)),
permalink: project_blob_path(project, File.join(@commit.id, @path))
)
end
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index b69d46f2c41..26f3c114108 100644
--- a/app/controllers/projects/cycle_analytics/events_controller.rb
+++ b/app/controllers/projects/cycle_analytics/events_controller.rb
@@ -2,7 +2,7 @@ module Projects
module CycleAnalytics
class EventsController < Projects::ApplicationController
include CycleAnalyticsParams
-
+
before_action :authorize_read_cycle_analytics!
before_action :authorize_read_build!, only: [:test, :staging]
before_action :authorize_read_issue!, only: [:issue, :production]
@@ -11,33 +11,33 @@ module Projects
def issue
render_events(cycle_analytics[:issue].events)
end
-
+
def plan
render_events(cycle_analytics[:plan].events)
end
-
+
def code
render_events(cycle_analytics[:code].events)
end
-
+
def test
options(events_params)[:branch] = events_params[:branch_name]
-
+
render_events(cycle_analytics[:test].events)
end
-
+
def review
render_events(cycle_analytics[:review].events)
end
-
+
def staging
render_events(cycle_analytics[:staging].events)
end
-
+
def production
render_events(cycle_analytics[:production].events)
end
-
+
private
def render_events(events)
@@ -46,14 +46,14 @@ module Projects
format.json { render json: { events: events } }
end
end
-
+
def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
end
-
+
def events_params
return {} unless params[:events].present?
-
+
params[:events].permit(:start_date, :branch_name)
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4de814d0ca8..2a3b73577a5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -218,8 +218,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_project_environment_deployment_path(environment.project, environment, deployment)
end
-
- metrics_monitoring_url =
+
+ metrics_monitoring_url =
if can?(current_user, :read_environment, environment)
environment_metrics_path(environment)
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index dc882b17143..16a74f82d3f 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -89,7 +89,7 @@ class UploadsController < ApplicationController
@uploader.retrieve_from_store!(params[:filename])
else
- @uploader = @model.send(upload_mount)
+ @uploader = @model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
redirect_to @uploader.url unless @uploader.file_storage?
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 69220a1c0f6..72e26b64e60 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -128,10 +128,10 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
- user = commit.send(options[:source])
+ user = commit.public_send(options[:source]) # rubocop:disable GitlabSecurity/PublicSend
- source_name = clean(commit.send "#{options[:source]}_name".to_sym)
- source_email = clean(commit.send "#{options[:source]}_email".to_sym)
+ source_name = clean(commit.public_send(:"#{options[:source]}_name")) # rubocop:disable GitlabSecurity/PublicSend
+ source_email = clean(commit.public_send(:"#{options[:source]}_email")) # rubocop:disable GitlabSecurity/PublicSend
person_name = user.try(:name) || source_name
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index a57b5a8fea5..a18ebfb6030 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -5,7 +5,7 @@ module ImportHelper
end
def provider_project_link(provider, path_with_namespace)
- url = __send__("#{provider}_project_url", path_with_namespace)
+ url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 70ea35fab1e..197c90c4081 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -174,7 +174,14 @@ module IssuablesHelper
end
def assigned_issuables_count(issuable_type)
- current_user.public_send("assigned_open_#{issuable_type}_count")
+ case issuable_type
+ when :issues
+ current_user.assigned_open_issues_count
+ when :merge_requests
+ current_user.assigned_open_merge_requests_count
+ else
+ raise ArgumentError, "invalid issuable `#{issuable_type}`"
+ end
end
def issuable_filter_params
@@ -298,10 +305,6 @@ module IssuablesHelper
cookies[:collapsed_gutter] == 'true'
end
- def base_issuable_scope(issuable)
- issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
- end
-
def issuable_state_scope(issuable)
if issuable.respond_to?(:merged?) && issuable.merged?
:merged
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index f8860bfee99..86666022a2a 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -32,7 +32,18 @@ module MilestonesHelper
end
def milestone_issues_by_label_count(milestone, label, state:)
- milestone.issues.with_label(label.title).send(state).size
+ issues = milestone.issues.with_label(label.title)
+ issues =
+ case state
+ when :opened
+ issues.opened
+ when :closed
+ issues.closed
+ else
+ raise ArgumentError, "invalid milestone state `#{state}`"
+ end
+
+ issues.size
end
# Returns count of milestones for different states
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
index fee1edc2a1b..6edaf78de1b 100644
--- a/app/helpers/pipeline_schedules_helper.rb
+++ b/app/helpers/pipeline_schedules_helper.rb
@@ -1,10 +1,10 @@
module PipelineSchedulesHelper
def timezone_data
- ActiveSupport::TimeZone.all.map do |timezone|
- {
- name: timezone.name,
- offset: timezone.utc_offset,
- identifier: timezone.tzinfo.identifier
+ ActiveSupport::TimeZone.all.map do |timezone|
+ {
+ name: timezone.name,
+ offset: timezone.utc_offset,
+ identifier: timezone.tzinfo.identifier
}
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 09cfd06dad3..bee4950e414 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -149,15 +149,16 @@ module ProjectsHelper
# 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?
- level = @project.project_feature.send(field)
disabled_option = ProjectFeature::ENABLED
highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
end
options = options_for_select(
options.invert,
- selected: highest_available_option || @project.project_feature.public_send(field),
+ selected: highest_available_option || level,
disabled: disabled_option
)
@@ -234,6 +235,8 @@ module ProjectsHelper
# If no limit is applied we'll just issue a COUNT since the result set could
# be too large to load into memory.
def any_projects?(projects)
+ return projects.any? if projects.is_a?(Array)
+
if projects.limit_value
projects.to_a.any?
else
@@ -486,7 +489,7 @@ module ProjectsHelper
end
def filename_path(project, filename)
- if project && blob = project.repository.send(filename)
+ if project && blob = project.repository.public_send(filename) # rubocop:disable GitlabSecurity/PublicSend
project_blob_path(
project,
tree_join(project.default_branch, blob.name)
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 3b175251446..456598b4c28 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -2,7 +2,7 @@ module VersionCheckHelper
def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled
image_url = VersionCheck.new.url
- image_tag image_url, class: 'js-version-status-badge', lazy: false
+ image_tag image_url, class: 'js-version-status-badge'
end
end
end
diff --git a/app/models/blob_viewer/notebook.rb b/app/models/blob_viewer/notebook.rb
index 8632b8a9885..e00b47e6c17 100644
--- a/app/models/blob_viewer/notebook.rb
+++ b/app/models/blob_viewer/notebook.rb
@@ -2,7 +2,7 @@ module BlobViewer
class Notebook < Base
include Rich
include ClientSide
-
+
self.partial_name = 'notebook'
self.extensions = %w(ipynb)
self.binary = false
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8be2dee6479..4692fb5644a 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -194,10 +194,7 @@ module Ci
# * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug
- ref.to_s
- .downcase
- .gsub(/[^a-z0-9]/, '-')[0..62]
- .gsub(/(\A-+|-+\z)/, '')
+ Gitlab::Utils.slugify(ref.to_s)
end
# Variables whose value does not depend on environment
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be9a56c190c..d41c88b4e30 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -200,7 +200,7 @@ class Commit
end
def method_missing(m, *args, &block)
- @raw.send(m, *args, &block)
+ @raw.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
def respond_to_missing?(method, include_private = false)
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 48547a938fc..193e459977a 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
- cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present?
+ cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false
@@ -93,14 +93,14 @@ module CacheMarkdownField
end
def attribute_invalidated?(attr)
- __send__("#{attr}_invalidated?")
+ __send__("#{attr}_invalidated?") # rubocop:disable GitlabSecurity/PublicSend
end
def cached_html_for(markdown_field)
raise ArgumentError.new("Unknown field: #{field}") unless
cached_markdown_fields.markdown_fields.include?(markdown_field)
- __send__(cached_markdown_fields.html_field(markdown_field))
+ __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
included do
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 67a0adfcd56..a3d0ac8d862 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -9,7 +9,7 @@ module InternalId
def set_iid
if iid.blank?
parent = project || group
- records = parent.send(self.class.name.tableize)
+ records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index c034bf9cbc0..1db6b2d2fa2 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -56,7 +56,7 @@ module Mentionable
end
self.class.mentionable_attrs.each do |attr, options|
- text = __send__(attr)
+ text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend
options = options.merge(
cache_key: [self, attr],
author: author,
@@ -100,7 +100,7 @@ module Mentionable
end
self.class.mentionable_attrs.any? do |attr, _|
- __send__(attr) =~ reference_pattern
+ __send__(attr) =~ reference_pattern # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 4865c0a14b1..ce69fd34ac5 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -82,7 +82,7 @@ module Participable
if attr.respond_to?(:call)
source.instance_exec(current_user, ext, &attr)
else
- process << source.__send__(attr)
+ process << source.__send__(attr) # rubocop:disable GitlabSecurity/PublicSend
end
end
when Enumerable, ActiveRecord::Relation
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 60734bc6660..cb59b4da3d7 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility
build_project_feature unless project_feature
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
- project_feature.send(:write_attribute, field, access_level)
+ project_feature.__send__(:write_attribute, field, access_level) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index ae8486bd9ac..b37b9bfbdac 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -12,7 +12,7 @@ class DeployKeysProject < ActiveRecord::Base
def destroy_orphaned_deploy_key
return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned?
-
+
self.deploy_key.destroy
end
end
diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb
index 8417f200e36..9357e55b419 100644
--- a/app/models/network/commit.rb
+++ b/app/models/network/commit.rb
@@ -12,7 +12,7 @@ module Network
end
def method_missing(m, *args, &block)
- @commit.send(m, *args, &block)
+ @commit.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
def space
diff --git a/app/models/project.rb b/app/models/project.rb
index 0de7da0ddaa..22b347cc8f9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -920,14 +920,14 @@ class Project < ActiveRecord::Base
end
def execute_hooks(data, hooks_scope = :push_hooks)
- hooks.send(hooks_scope).each do |hook|
+ hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, hooks_scope.to_s)
end
end
def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
- services.send(hooks_scope).each do |service|
+ services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
service.async_execute(data)
end
end
@@ -1282,12 +1282,16 @@ class Project < ActiveRecord::Base
status.zero?
end
+ def full_path_slug
+ Gitlab::Utils.slugify(full_path.to_s)
+ end
+
def predefined_variables
[
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: full_path.parameterize, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
]
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index 6d1a321f651..7b15a5dd04d 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -115,7 +115,7 @@ class ChatNotificationService < Service
def get_channel_field(event)
field_name = event_channel_name(event)
- self.public_send(field_name)
+ self.public_send(field_name) # rubocop:disable GitlabSecurity/PublicSend
end
def build_event_channels
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index e3906943ecd..f422e0ea036 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -53,7 +53,7 @@ class HipchatService < Service
return unless supported_events.include?(data[:object_kind])
message = create_message(data)
return unless message.present?
- gate[room].send('GitLab', message, message_options(data))
+ gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
end
def test(data)
diff --git a/app/models/protectable_dropdown.rb b/app/models/protectable_dropdown.rb
index 122fbce257d..c96edc5a259 100644
--- a/app/models/protectable_dropdown.rb
+++ b/app/models/protectable_dropdown.rb
@@ -1,5 +1,9 @@
class ProtectableDropdown
+ REF_TYPES = %i[branches tags].freeze
+
def initialize(project, ref_type)
+ raise ArgumentError, "invalid ref type `#{ref_type}`" unless ref_type.in?(REF_TYPES)
+
@project = project
@ref_type = ref_type
end
@@ -16,7 +20,7 @@ class ProtectableDropdown
private
def refs
- @project.repository.public_send(@ref_type)
+ @project.repository.public_send(@ref_type) # rubocop:disable GitlabSecurity/PublicSend
end
def ref_names
@@ -24,7 +28,7 @@ class ProtectableDropdown
end
def protections
- @project.public_send("protected_#{@ref_type}")
+ @project.public_send("protected_#{@ref_type}") # rubocop:disable GitlabSecurity/PublicSend
end
def non_wildcard_protected_ref_names
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 090fbd61e6f..31de204d824 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -14,7 +14,7 @@ class RedirectRoute < ActiveRecord::Base
else
'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
end
-
+
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a761302b06b..c1e4fcf94a4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -48,7 +48,9 @@ class Repository
alias_method(original, name)
define_method(name) do
- cache_method_output(name, fallback: fallback, memoize_only: memoize_only) { __send__(original) }
+ cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
+ __send__(original) # rubocop:disable GitlabSecurity/PublicSend
+ end
end
end
@@ -443,9 +445,9 @@ class Repository
def method_missing(m, *args, &block)
if m == :lookup && !block_given?
lookup_cache[m] ||= {}
- lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block)
+ lookup_cache[m][args.join(":")] ||= raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
else
- raw_repository.send(m, *args, &block)
+ raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
end
@@ -776,7 +778,7 @@ class Repository
end
actions.each do |options|
- index.public_send(options.delete(:action), options)
+ index.public_send(options.delete(:action), options) # rubocop:disable GitlabSecurity/PublicSend
end
options = {
diff --git a/app/models/user.rb b/app/models/user.rb
index 2b25736bb26..0e2654ff757 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1070,7 +1070,7 @@ class User < ActiveRecord::Base
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
def send_devise_notification(notification, *args)
return true unless can?(:receive_notifications)
- devise_mailer.send(notification, self, *args).deliver_later
+ devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
end
# This works around a bug in Devise 4.2.0 that erroneously causes a user to
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index dc283ba3e7a..b3e5fd21e97 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -1,6 +1,6 @@
class ProjectEntity < Grape::Entity
include RequestAwareEntity
-
+
expose :id
expose :name
diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb
index 23b65aa4a4c..69702ae1493 100644
--- a/app/serializers/tree_root_entity.rb
+++ b/app/serializers/tree_root_entity.rb
@@ -1,8 +1,21 @@
# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`.
class TreeRootEntity < Grape::Entity
+ include RequestAwareEntity
+
expose :path
-
+
expose :trees, using: TreeEntity
expose :blobs, using: BlobEntity
expose :submodules, using: SubmoduleEntity
+
+ expose :parent_tree_url do |tree|
+ path = tree.path.sub(%r{\A/}, '')
+ next unless path.present?
+
+ path_segments = path.split('/')
+ path_segments.pop
+ parent_tree_path = path_segments.join('/')
+
+ project_tree_path(request.project, File.join(request.ref, parent_tree_path))
+ end
end
diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb
index 8e11a2a36a7..59153cbbc0a 100644
--- a/app/services/akismet_service.rb
+++ b/app/services/akismet_service.rb
@@ -58,7 +58,7 @@ class AkismetService
}
begin
- akismet_client.public_send(type, options[:ip_address], options[:user_agent], params)
+ akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
true
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 6372e5755db..ea3b8d66ed9 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -23,7 +23,7 @@ module Ci
end
attributes = CLONE_ACCESSORS.map do |attribute|
- [attribute, build.send(attribute)]
+ [attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end
attributes.push([:user, current_user])
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index a48d6a976f0..85c2fcf9ea6 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -11,6 +11,7 @@ module Commits
def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action)
+ # rubocop:disable GitlabSecurity/PublicSend
repository.public_send(
action,
current_user,
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index b84a6fd2b7d..4a4f2b91182 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -338,7 +338,7 @@ class IssuableBaseService < BaseService
def invalidate_cache_counts(issuable, users: [], skip_project_cache: false)
users.each do |user|
- user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts")
+ user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend
end
unless skip_project_cache
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 2e089149ca8..46c505baf8b 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -31,7 +31,7 @@ module Members
source.members.find_by(condition) ||
source.requesters.find_by!(condition)
else
- source.public_send(scope).find_by!(condition)
+ source.public_send(scope).find_by!(condition) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index fa0c0b7175c..194413bf321 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -25,7 +25,6 @@ module MergeRequests
end
def after_create(issuable)
- event_service.open_mr(issuable, current_user)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
update_merge_requests_head_pipeline(issuable)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 4267879b03d..e2a80db06a6 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -1,3 +1,5 @@
+# rubocop:disable GitlabSecurity/PublicSend
+
# NotificationService class
#
# Used for notifying users with emails about different events
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index cbcd4478af6..a1c2f8d0180 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -4,7 +4,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
- SystemHook.public_send(hooks_scope).find_each do |hook|
+ SystemHook.public_send(hooks_scope).find_each do |hook| # rubocop:disable GitlabSecurity/PublicSend
hook.async_execute(data, 'system_hooks')
end
end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index 74ba814afff..4abd2c44b2f 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -18,7 +18,7 @@ module TestHooks
end
error_message = catch(:validation_error) do
- sample_data = self.__send__(trigger_data_method)
+ sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
return hook.execute(sample_data, trigger)
end
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index f18c3a74120..445f0dffbcc 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -189,7 +189,7 @@
= icon('chevron-down')
%ul.dropdown-menu
%li
- %a Sort by date
+ = link_to 'Sort by date', '#'
= link_to 'New issue', '#', class: 'btn btn-new btn-inverted'
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index 0b4a9d92bea..3cbcd841aff 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -1,150 +1,151 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .context-header
- = link_to admin_root_path, title: 'Admin Overview' do
- .avatar-container.s40.settings-avatar
- = icon('wrench')
- .project-title Admin Area
- %ul.sidebar-top-level-items
- = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
- .nav-icon-container
- = custom_icon('overview')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview' do
- %span
- Dashboard
- = nav_link(controller: [:admin, :projects]) do
- = link_to admin_projects_path, title: 'Projects' do
- %span
- Projects
- = nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users' do
- %span
- Users
- = nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link path: 'jobs#index' do
- = link_to admin_jobs_path, title: 'Jobs' do
- %span
- Jobs
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path, title: 'Runners' do
- %span
- Runners
- = nav_link path: 'cohorts#index' do
- = link_to admin_cohorts_path, title: 'Cohorts' do
- %span
- Cohorts
+ .nav-sidebar-inner-scroll
+ .context-header
+ = link_to admin_root_path, title: 'Admin Overview' do
+ .avatar-container.s40.settings-avatar
+ = icon('wrench')
+ .project-title Admin Area
+ %ul.sidebar-top-level-items
+ = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('overview')
+ %span.nav-item-name
+ Overview
- = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
- = link_to admin_conversational_development_index_path, title: 'Monitoring' do
- .nav-icon-container
- = custom_icon('monitoring')
- %span.nav-item-name
- Monitoring
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview' do
+ %span
+ Dashboard
+ = nav_link(controller: [:admin, :projects]) do
+ = link_to admin_projects_path, title: 'Projects' do
+ %span
+ Projects
+ = nav_link(controller: :users) do
+ = link_to admin_users_path, title: 'Users' do
+ %span
+ Users
+ = nav_link(controller: :groups) do
+ = link_to admin_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link path: 'jobs#index' do
+ = link_to admin_jobs_path, title: 'Jobs' do
+ %span
+ Jobs
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path, title: 'Runners' do
+ %span
+ Runners
+ = nav_link path: 'cohorts#index' do
+ = link_to admin_cohorts_path, title: 'Cohorts' do
+ %span
+ Cohorts
- %ul.sidebar-sub-level-items
- = nav_link(controller: :conversational_development_index) do
- = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
- %span
- ConvDev Index
- = nav_link(controller: :system_info) do
- = link_to admin_system_info_path, title: 'System Info' do
- %span
- System Info
- = nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs' do
- %span
- Background Jobs
- = nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs' do
- %span
- Logs
- = nav_link(controller: :health_check) do
- = link_to admin_health_check_path, title: 'Health Check' do
- %span
- Health Check
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
- %span
- Requests Profiles
+ = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
+ = link_to admin_conversational_development_index_path, title: 'Monitoring' do
+ .nav-icon-container
+ = custom_icon('monitoring')
+ %span.nav-item-name
+ Monitoring
- = nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Messages' do
- .nav-icon-container
- = custom_icon('messages')
- %span.nav-item-name
- Messages
- = nav_link(controller: [:hooks, :hook_logs]) do
- = link_to admin_hooks_path, title: 'Hooks' do
- .nav-icon-container
- = custom_icon('system_hooks')
- %span.nav-item-name
- System Hooks
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :conversational_development_index) do
+ = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
+ %span
+ ConvDev Index
+ = nav_link(controller: :system_info) do
+ = link_to admin_system_info_path, title: 'System Info' do
+ %span
+ System Info
+ = nav_link(controller: :background_jobs) do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %span
+ Background Jobs
+ = nav_link(controller: :logs) do
+ = link_to admin_logs_path, title: 'Logs' do
+ %span
+ Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ %span
+ Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
- = nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications' do
- .nav-icon-container
- = custom_icon('applications')
- %span.nav-item-name
- Applications
+ = nav_link(controller: :broadcast_messages) do
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
+ .nav-icon-container
+ = custom_icon('messages')
+ %span.nav-item-name
+ Messages
+ = nav_link(controller: [:hooks, :hook_logs]) do
+ = link_to admin_hooks_path, title: 'Hooks' do
+ .nav-icon-container
+ = custom_icon('system_hooks')
+ %span.nav-item-name
+ System Hooks
- = nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse Reports" do
- .nav-icon-container
- = custom_icon('abuse_reports')
- %span.nav-item-name
- Abuse Reports
- %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ .nav-icon-container
+ = custom_icon('applications')
+ %span.nav-item-name
+ Applications
- - if akismet_enabled?
- = nav_link(controller: :spam_logs) do
- = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = nav_link(controller: :abuse_reports) do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
.nav-icon-container
- = custom_icon('spam_logs')
+ = custom_icon('abuse_reports')
%span.nav-item-name
- Spam Logs
+ Abuse Reports
+ %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- = nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
- .nav-icon-container
- = custom_icon('key')
- %span.nav-item-name
- Deploy Keys
+ - if akismet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ .nav-icon-container
+ = custom_icon('spam_logs')
+ %span.nav-item-name
+ Spam Logs
- = nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates' do
- .nav-icon-container
- = custom_icon('service_templates')
- %span.nav-item-name
- Service Templates
+ = nav_link(controller: :deploy_keys) do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+ .nav-icon-container
+ = custom_icon('key')
+ %span.nav-item-name
+ Deploy Keys
- = nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels' do
- .nav-icon-container
- = custom_icon('labels')
- %span.nav-item-name
- Labels
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ .nav-icon-container
+ = custom_icon('service_templates')
+ %span.nav-item-name
+ Service Templates
- = nav_link(controller: :appearances) do
- = link_to admin_appearances_path, title: 'Appearances' do
- .nav-icon-container
- = custom_icon('appearance')
- %span.nav-item-name
- Appearance
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels' do
+ .nav-icon-container
+ = custom_icon('labels')
+ %span.nav-item-name
+ Labels
- %li.divider
- = nav_link(controller: :application_settings) do
- = link_to admin_application_settings_path, title: 'Settings' do
- .nav-icon-container
- = custom_icon('settings')
- %span.nav-item-name
- Settings
+ = nav_link(controller: :appearances) do
+ = link_to admin_appearances_path, title: 'Appearances' do
+ .nav-icon-container
+ = custom_icon('appearance')
+ %span.nav-item-name
+ Appearance
+
+ %li.divider
+ = nav_link(controller: :application_settings) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ Settings
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index c7dabbd8237..ed5793f09fe 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,89 +1,90 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .context-header
- = link_to group_path(@group), title: @group.name do
- .avatar-container.s40.group-avatar
- = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
- .group-title
- = @group.name
- %ul.sidebar-top-level-items
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Group overview' do
- .nav-icon-container
- = custom_icon('project')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Group details' do
- %span
- Details
-
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- %span
- Activity
-
- = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
- = link_to issues_group_path(@group), title: 'Issues' do
- .nav-icon-container
- = custom_icon('issues')
- %span.nav-item-name
- - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- Issues
- %span.badge.count= number_with_delimiter(issues.count)
-
- %ul.sidebar-sub-level-items
- = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
- = link_to issues_group_path(@group), title: 'List' do
- %span
- List
+ .nav-sidebar-inner-scroll
+ .context-header
+ = link_to group_path(@group), title: @group.name do
+ .avatar-container.s40.group-avatar
+ = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
+ .group-title
+ = @group.name
+ %ul.sidebar-top-level-items
+ = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Group overview' do
+ .nav-icon-container
+ = custom_icon('project')
+ %span.nav-item-name
+ Overview
- = nav_link(path: 'labels#index') do
- = link_to group_labels_path(@group), title: 'Labels' do
- %span
- Labels
+ %ul.sidebar-sub-level-items
+ = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Group details' do
+ %span
+ Details
- = nav_link(path: 'milestones#index') do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- %span
- Milestones
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ %span
+ Activity
- = nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
- .nav-icon-container
- = custom_icon('mr_bold')
- %span.nav-item-name
- - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
- Merge Requests
- %span.badge.count= number_with_delimiter(merge_requests.count)
- = nav_link(path: 'group_members#index') do
- = link_to group_group_members_path(@group), title: 'Members' do
- .nav-icon-container
- = custom_icon('members')
- %span.nav-item-name
- Members
- - if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
- = link_to edit_group_path(@group), title: 'Settings' do
+ = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
+ = link_to issues_group_path(@group), title: 'Issues' do
.nav-icon-container
- = custom_icon('settings')
+ = custom_icon('issues')
%span.nav-item-name
- Settings
+ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ Issues
+ %span.badge.count= number_with_delimiter(issues.count)
+
%ul.sidebar-sub-level-items
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'General' do
+ = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
+ = link_to issues_group_path(@group), title: 'List' do
%span
- General
+ List
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
+ = nav_link(path: 'labels#index') do
+ = link_to group_labels_path(@group), title: 'Labels' do
%span
- Projects
+ Labels
- = nav_link(controller: :ci_cd) do
- = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
+ = nav_link(path: 'milestones#index') do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
%span
- CI / CD
+ Milestones
+
+ = nav_link(path: 'groups#merge_requests') do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ .nav-icon-container
+ = custom_icon('mr_bold')
+ %span.nav-item-name
+ - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+ Merge Requests
+ %span.badge.count= number_with_delimiter(merge_requests.count)
+ = nav_link(path: 'group_members#index') do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ .nav-icon-container
+ = custom_icon('members')
+ %span.nav-item-name
+ Members
+ - if current_user && can?(current_user, :admin_group, @group)
+ = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
+ = link_to edit_group_path(@group), title: 'Settings' do
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ Settings
+ %ul.sidebar-sub-level-items
+ = nav_link(path: 'groups#edit') do
+ = link_to edit_group_path(@group), title: 'General' do
+ %span
+ General
+
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects' do
+ %span
+ Projects
+
+ = nav_link(controller: :ci_cd) do
+ = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
+ %span
+ CI / CD
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
index edae009a28e..4234df56d1d 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -1,84 +1,85 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .context-header
- = link_to profile_path, title: 'Profile Settings' do
- .avatar-container.s40.settings-avatar
- = icon('user')
- .project-title User Settings
- %ul.sidebar-top-level-items
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ .nav-sidebar-inner-scroll
+ .context-header
= link_to profile_path, title: 'Profile Settings' do
- .nav-icon-container
- = custom_icon('profile')
- %span.nav-item-name
- Profile
- = nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account' do
- .nav-icon-container
- = custom_icon('account')
- %span.nav-item-name
- Account
- - if current_application_settings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
+ .avatar-container.s40.settings-avatar
+ = icon('user')
+ .project-title User Settings
+ %ul.sidebar-top-level-items
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: 'Profile Settings' do
.nav-icon-container
- = custom_icon('applications')
+ = custom_icon('profile')
%span.nav-item-name
- Applications
- = nav_link(controller: :chat_names) do
- = link_to profile_chat_names_path, title: 'Chat' do
- .nav-icon-container
- = custom_icon('chat')
- %span.nav-item-name
- Chat
- = nav_link(controller: :personal_access_tokens) do
- = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
- .nav-icon-container
- = custom_icon('access_tokens')
- %span.nav-item-name
- Access Tokens
- = nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails' do
- .nav-icon-container
- = custom_icon('emails')
- %span.nav-item-name
- Emails
- - unless current_user.ldap_user?
- = nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password' do
+ Profile
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
+ = link_to profile_account_path, title: 'Account' do
.nav-icon-container
- = custom_icon('lock')
+ = custom_icon('account')
%span.nav-item-name
- Password
- = nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications' do
- .nav-icon-container
- = custom_icon('notifications')
- %span.nav-item-name
- Notifications
+ Account
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ .nav-icon-container
+ = custom_icon('applications')
+ %span.nav-item-name
+ Applications
+ = nav_link(controller: :chat_names) do
+ = link_to profile_chat_names_path, title: 'Chat' do
+ .nav-icon-container
+ = custom_icon('chat')
+ %span.nav-item-name
+ Chat
+ = nav_link(controller: :personal_access_tokens) do
+ = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+ .nav-icon-container
+ = custom_icon('access_tokens')
+ %span.nav-item-name
+ Access Tokens
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path, title: 'Emails' do
+ .nav-icon-container
+ = custom_icon('emails')
+ %span.nav-item-name
+ Emails
+ - unless current_user.ldap_user?
+ = nav_link(controller: :passwords) do
+ = link_to edit_profile_password_path, title: 'Password' do
+ .nav-icon-container
+ = custom_icon('lock')
+ %span.nav-item-name
+ Password
+ = nav_link(controller: :notifications) do
+ = link_to profile_notifications_path, title: 'Notifications' do
+ .nav-icon-container
+ = custom_icon('notifications')
+ %span.nav-item-name
+ Notifications
- = nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys' do
- .nav-icon-container
- = custom_icon('key')
- %span.nav-item-name
- SSH Keys
- = nav_link(controller: :gpg_keys) do
- = link_to profile_gpg_keys_path, title: 'GPG Keys' do
- .nav-icon-container
- = custom_icon('key_2')
- %span.nav-item-name
- GPG Keys
- = nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences' do
- .nav-icon-container
- = custom_icon('preferences')
- %span.nav-item-name
- Preferences
- = nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Authentication log' do
- .nav-icon-container
- = custom_icon('authentication_log')
- %span.nav-item-name
- Authentication log
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ .nav-icon-container
+ = custom_icon('key')
+ %span.nav-item-name
+ SSH Keys
+ = nav_link(controller: :gpg_keys) do
+ = link_to profile_gpg_keys_path, title: 'GPG Keys' do
+ .nav-icon-container
+ = custom_icon('key_2')
+ %span.nav-item-name
+ GPG Keys
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences' do
+ .nav-icon-container
+ = custom_icon('preferences')
+ %span.nav-item-name
+ Preferences
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Authentication log' do
+ .nav-icon-container
+ = custom_icon('authentication_log')
+ %span.nav-item-name
+ Authentication log
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index e0477c29ebe..0ef81375c3a 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,261 +1,262 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- - can_edit = can?(current_user, :admin_project, @project)
- .context-header
- = link_to project_path(@project), title: @project.name do
- .avatar-container.s40.project-avatar
- = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
- .project-title
- = @project.name
- %ul.sidebar-top-level-items
- = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
- = link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do
- .nav-icon-container
- = custom_icon('project')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(path: 'projects#show') do
- = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
- %span= _('Details')
-
- = nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
- %span= _('Activity')
-
- - if can?(current_user, :read_cycle_analytics, @project)
- = nav_link(path: 'cycle_analytics#show') do
- = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
- %span= _('Cycle Analytics')
-
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
- = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ .nav-sidebar-inner-scroll
+ - can_edit = can?(current_user, :admin_project, @project)
+ .context-header
+ = link_to project_path(@project), title: @project.name do
+ .avatar-container.s40.project-avatar
+ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
+ .project-title
+ = @project.name
+ %ul.sidebar-top-level-items
+ = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
+ = link_to project_path(@project), title: 'Project overview', class: 'shortcuts-project' do
.nav-icon-container
- = custom_icon('doc_text')
+ = custom_icon('project')
%span.nav-item-name
- Repository
+ Overview
%ul.sidebar-sub-level-items
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_tree_path(@project) do
- #{ _('Files') }
-
- = nav_link(controller: [:commit, :commits]) do
- = link_to project_commits_path(@project, current_ref) do
- #{ _('Commits') }
-
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
- #{ _('Branches') }
-
- = nav_link(controller: [:tags, :releases]) do
- = link_to project_tags_path(@project) do
- #{ _('Tags') }
-
- = nav_link(path: 'graphs#show') do
- = link_to project_graph_path(@project, current_ref) do
- #{ _('Contributors') }
-
- = nav_link(controller: %w(network)) do
- = link_to project_network_path(@project, current_ref) do
- #{ s_('ProjectNetworkGraph|Graph') }
-
- = nav_link(controller: :compare) do
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
- #{ _('Compare') }
-
- = nav_link(path: 'graphs#charts') do
- = link_to charts_project_graph_path(@project, current_ref) do
- #{ _('Charts') }
-
- - if project_nav_tab? :container_registry
- = nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
- .nav-icon-container
- = custom_icon('container_registry')
- %span.nav-item-name
- Registry
-
- - if project_nav_tab? :issues
- = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
- .nav-icon-container
- = custom_icon('issues')
- %span.nav-item-name
- Issues
- - if @project.issues_enabled?
- %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
-
- %ul.sidebar-sub-level-items
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), title: 'Issues' do
- %span
- List
-
- = nav_link(controller: :boards) do
- = link_to project_boards_path(@project), title: 'Board' do
- %span
- Board
-
- = nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: 'Labels' do
- %span
- Labels
-
- = nav_link(controller: :milestones) do
- = link_to project_milestones_path(@project), title: 'Milestones' do
- %span
- Milestones
-
- - if project_nav_tab? :merge_requests
- = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
- .nav-icon-container
- = custom_icon('mr_bold')
- %span.nav-item-name
- Merge Requests
- %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
-
- - if project_nav_tab? :pipelines
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
- = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do
- .nav-icon-container
- = custom_icon('pipeline')
- %span.nav-item-name
- CI / CD
-
- %ul.sidebar-sub-level-items
- - if project_nav_tab? :pipelines
- = nav_link(path: ['pipelines#index', 'pipelines#show']) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
-
- - if project_nav_tab? :builds
- = nav_link(controller: [:jobs, :artifacts]) do
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ = nav_link(path: 'projects#show') do
+ = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
+ %span= _('Details')
+
+ = nav_link(path: 'projects#activity') do
+ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
+ %span= _('Activity')
+
+ - if can?(current_user, :read_cycle_analytics, @project)
+ = nav_link(path: 'cycle_analytics#show') do
+ = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
+ %span= _('Cycle Analytics')
+
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
+ = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('doc_text')
+ %span.nav-item-name
+ Repository
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_tree_path(@project) do
+ #{ _('Files') }
+
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to project_commits_path(@project, current_ref) do
+ #{ _('Commits') }
+
+ = nav_link(html_options: {class: branches_tab_class}) do
+ = link_to project_branches_path(@project) do
+ #{ _('Branches') }
+
+ = nav_link(controller: [:tags, :releases]) do
+ = link_to project_tags_path(@project) do
+ #{ _('Tags') }
+
+ = nav_link(path: 'graphs#show') do
+ = link_to project_graph_path(@project, current_ref) do
+ #{ _('Contributors') }
+
+ = nav_link(controller: %w(network)) do
+ = link_to project_network_path(@project, current_ref) do
+ #{ s_('ProjectNetworkGraph|Graph') }
+
+ = nav_link(controller: :compare) do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
+ #{ _('Compare') }
+
+ = nav_link(path: 'graphs#charts') do
+ = link_to charts_project_graph_path(@project, current_ref) do
+ #{ _('Charts') }
+
+ - if project_nav_tab? :container_registry
+ = nav_link(controller: %w[projects/registry/repositories]) do
+ = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ .nav-icon-container
+ = custom_icon('container_registry')
+ %span.nav-item-name
+ Registry
+
+ - if project_nav_tab? :issues
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
+ .nav-icon-container
+ = custom_icon('issues')
+ %span.nav-item-name
+ Issues
+ - if @project.issues_enabled?
+ %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :issues) do
+ = link_to project_issues_path(@project), title: 'Issues' do
%span
- Jobs
+ List
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipeline_schedules) do
- = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+ = nav_link(controller: :boards) do
+ = link_to project_boards_path(@project), title: 'Board' do
%span
- Schedules
+ Board
- - if project_nav_tab? :environments
- = nav_link(controller: :environments) do
- = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ = nav_link(controller: :labels) do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
- Environments
+ Labels
- - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
- = nav_link(path: 'pipelines#charts') do
- = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ = nav_link(controller: :milestones) do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
- Charts
+ Milestones
+
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ .nav-icon-container
+ = custom_icon('mr_bold')
+ %span.nav-item-name
+ Merge Requests
+ %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
+ = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do
+ .nav-icon-container
+ = custom_icon('pipeline')
+ %span.nav-item-name
+ CI / CD
+
+ %ul.sidebar-sub-level-items
+ - if project_nav_tab? :pipelines
+ = nav_link(path: ['pipelines#index', 'pipelines#show']) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
- .nav-icon-container
- = custom_icon('wiki')
- %span.nav-item-name
- Wiki
+ - if project_nav_tab? :builds
+ = nav_link(controller: [:jobs, :artifacts]) do
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ %span
+ Jobs
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
- .nav-icon-container
- = custom_icon('snippets')
- %span.nav-item-name
- Snippets
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipeline_schedules) do
+ = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+ %span
+ Schedules
- - if project_nav_tab? :settings
- = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
- = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
- .nav-icon-container
- = custom_icon('settings')
- %span.nav-item-name
- Settings
+ - if project_nav_tab? :environments
+ = nav_link(controller: :environments) do
+ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ %span
+ Environments
- %ul.sidebar-sub-level-items
- - can_edit = can?(current_user, :admin_project, @project)
- - if can_edit
- = nav_link(path: %w[projects#edit]) do
- = link_to edit_project_path(@project), title: 'General' do
- %span
- General
- = nav_link(controller: :project_members) do
- = link_to project_project_members_path(@project), title: 'Members' do
- %span
- Members
- - if can_edit
- = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
- = link_to project_settings_integrations_path(@project), title: 'Integrations' do
- %span
- Integrations
- = nav_link(controller: :repository) do
- = link_to project_settings_repository_path(@project), title: 'Repository' do
+ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
+ = nav_link(path: 'pipelines#charts') do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ %span
+ Charts
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+ .nav-icon-container
+ = custom_icon('wiki')
+ %span.nav-item-name
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
+ .nav-icon-container
+ = custom_icon('snippets')
+ %span.nav-item-name
+ Snippets
+
+ - if project_nav_tab? :settings
+ = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
+ = link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ Settings
+
+ %ul.sidebar-sub-level-items
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if can_edit
+ = nav_link(path: %w[projects#edit]) do
+ = link_to edit_project_path(@project), title: 'General' do
+ %span
+ General
+ = nav_link(controller: :project_members) do
+ = link_to project_project_members_path(@project), title: 'Members' do
%span
- Repository
- - if @project.feature_available?(:builds, current_user)
- = nav_link(controller: :ci_cd) do
- = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
+ Members
+ - if can_edit
+ = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
+ = link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
- CI / CD
- - if Gitlab.config.pages.enabled
- = nav_link(controller: :pages) do
- = link_to project_pages_path(@project), title: 'Pages' do
+ Integrations
+ = nav_link(controller: :repository) do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
%span
- Pages
-
- - else
- = nav_link(path: %w[members#show]) do
- = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
- .nav-icon-container
- = custom_icon('members')
- %span.nav-item-name
- Members
-
- = render 'shared/sidebar_toggle_button'
-
- -# Shortcut to Project > Activity
- %li.hidden
- = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
- %span
- Activity
-
- -# Shortcut to Repository > Graph (formerly, Network)
- - if project_nav_tab? :network
+ Repository
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(controller: :ci_cd) do
+ = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
+ %span
+ CI / CD
+ - if Gitlab.config.pages.enabled
+ = nav_link(controller: :pages) do
+ = link_to project_pages_path(@project), title: 'Pages' do
+ %span
+ Pages
+
+ - else
+ = nav_link(path: %w[members#show]) do
+ = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('members')
+ %span.nav-item-name
+ Members
+
+ = render 'shared/sidebar_toggle_button'
+
+ -# Shortcut to Project > Activity
%li.hidden
- = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
- Graph
-
- -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- - unless @project.empty_repo?
- %li.hidden
- = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
- Charts
-
- -# Shortcut to Issues > New Issue
- %li.hidden
- = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
- Create a new issue
-
- -# Shortcut to Pipelines > Jobs
- - if project_nav_tab? :builds
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+ %span
+ Activity
+
+ -# Shortcut to Repository > Graph (formerly, Network)
+ - if project_nav_tab? :network
+ %li.hidden
+ = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ Graph
+
+ -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
+ - unless @project.empty_repo?
+ %li.hidden
+ = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ Charts
+
+ -# Shortcut to Issues > New Issue
%li.hidden
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
- Jobs
-
- -# Shortcut to commits page
- - if project_nav_tab? :commits
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
+ Create a new issue
+
+ -# Shortcut to Pipelines > Jobs
+ - if project_nav_tab? :builds
+ %li.hidden
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ Jobs
+
+ -# Shortcut to commits page
+ - if project_nav_tab? :commits
+ %li.hidden
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+ Commits
+
+ -# Shortcut to issue boards
%li.hidden
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
- Commits
-
- -# Shortcut to issue boards
- %li.hidden
- = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 178ab3df2e5..376f672f424 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -6,7 +6,7 @@
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) }
.files-changed-inner
- .inline-parallel-buttons
+ .inline-parallel-buttons.hidden-xs.hidden-sm
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index efc0ea31917..02fd54c97fb 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -10,7 +10,7 @@
%strong.cgreen #{sum_added_lines} additions
and
%strong.cred #{sum_removed_lines} deletions
- .diff-stats-additions-deletions-collapsed.pull-right{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
+ .diff-stats-additions-deletions-collapsed.pull-right.hidden-xs.hidden-sm{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
%strong.cgreen<
+#{sum_added_lines}
%strong.cred<
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index b787edb3427..3303aa72604 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -4,8 +4,8 @@
= link_to 'Close merge request', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: { original_text: "Close merge request", alternative_text: "Comment & close merge request"}
- if @merge_request.reopenable?
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-close js-note-target-reopen", title: "Reopen merge request", data: { original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
- %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
- %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
- {{ buttonText }}
+ %comment-and-resolve-btn{ "inline-template" => true }
+ %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
+ {{ buttonText }}
#notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index 436d1aa9e57..e73dab8ad4a 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- if @can_bulk_update
- = button_tag "Edit merge requests", class: "btn js-bulk-update-toggle"
+ = button_tag "Edit merge requests", class: "btn append-right-10 js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
New merge request
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 4498c8f8349..7ad743b3b81 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -6,7 +6,7 @@
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags")
diff --git a/app/views/shared/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml
index 3672b552f10..9236868652f 100644
--- a/app/views/shared/_target_switcher.html.haml
+++ b/app/views/shared/_target_switcher.html.haml
@@ -1,5 +1,5 @@
- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag nil, method: :get, class: "project-refs-target-form" do
+= form_tag nil, method: :get, style: { display: 'none' }, class: "project-refs-target-form" do
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
diff --git a/app/views/shared/icons/_node_express.svg b/app/views/shared/icons/_express.svg
index f2c94319f19..f2c94319f19 100644
--- a/app/views/shared/icons/_node_express.svg
+++ b/app/views/shared/icons/_express.svg
diff --git a/app/views/shared/icons/_java_spring.svg b/app/views/shared/icons/_spring.svg
index 508349aa456..508349aa456 100644
--- a/app/views/shared/icons/_java_spring.svg
+++ b/app/views/shared/icons/_spring.svg
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 6f6a036b13f..6a85f7d0564 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -32,7 +32,7 @@
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestones, @group)
- if milestone.is_group_milestone?
- = link_to edit_group_milestone_path(@group, milestone.id), class: "btn btn-xs btn-grouped" do
+ = link_to edit_group_milestone_path(@group, milestone), class: "btn btn-xs btn-grouped" do
Edit
\
- if milestone.closed?
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index b93837e3087..3014300fbe7 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -23,7 +23,7 @@
.pull-right
- if can?(current_user, :admin_milestones, group)
- if milestone.is_group_milestone?
- = link_to edit_group_milestone_path(group, milestone.iid), class: "btn btn btn-grouped" do
+ = link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do
Edit
- if milestone.active?
= link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml
index 0fc40cf0801..87fa2007d16 100644
--- a/app/views/shared/repo/_repo.html.haml
+++ b/app/views/shared/repo/_repo.html.haml
@@ -1,2 +1,7 @@
-#repo{ data: { url: content_url, project_name: project.name, refs_url: refs_project_path(project, format: :json), project_url: project_path(project), project_id: project.id, can_commit: (!!can_push_branch?(project, @ref)).to_s } }
- %repo
+#repo{ data: { url: content_url,
+ project_name: project.name,
+ refs_url: refs_project_path(project, format: :json),
+ project_url: project_path(project),
+ project_id: project.id,
+ can_commit: (!!can_push_branch?(project, @ref)).to_s,
+ on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } }
diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb
index 964287a1793..0ec871e00e1 100644
--- a/app/workers/gitlab_shell_worker.rb
+++ b/app/workers/gitlab_shell_worker.rb
@@ -4,6 +4,6 @@ class GitlabShellWorker
include DedicatedSidekiqQueue
def perform(action, *arg)
- gitlab_shell.send(action, *arg)
+ gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml b/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml
new file mode 100644
index 00000000000..278ef2a8acb
--- /dev/null
+++ b/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml
@@ -0,0 +1,4 @@
+---
+title: Added tests for commits API unauthenticated user and public/private project
+merge_request: 13287
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/34643-fix-project-path-slugify.yml b/changelogs/unreleased/34643-fix-project-path-slugify.yml
new file mode 100644
index 00000000000..f7018a1aca5
--- /dev/null
+++ b/changelogs/unreleased/34643-fix-project-path-slugify.yml
@@ -0,0 +1,4 @@
+---
+title: Fix CI_PROJECT_PATH_SLUG slugify
+merge_request: 13350
+author: Ivan Chernov
diff --git a/changelogs/unreleased/commits-list-page-limit.yml b/changelogs/unreleased/commits-list-page-limit.yml
new file mode 100644
index 00000000000..2fd54c5960a
--- /dev/null
+++ b/changelogs/unreleased/commits-list-page-limit.yml
@@ -0,0 +1,5 @@
+---
+title: Fix commit list not loading the correct page when scrolling
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-btn-alignment.yml b/changelogs/unreleased/fix-btn-alignment.yml
new file mode 100644
index 00000000000..e5dce3d3a0e
--- /dev/null
+++ b/changelogs/unreleased/fix-btn-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix inconsistent spacing for edit buttons on issues and merge request page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_31790.yml b/changelogs/unreleased/issue_31790.yml
new file mode 100644
index 00000000000..df02cad423a
--- /dev/null
+++ b/changelogs/unreleased/issue_31790.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API responses when dealing with txt files
+merge_request:
+author:
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e73db08fcac..25285525846 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -649,6 +649,9 @@ test:
default:
path: tmp/tests/repositories/
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
+ failure_count_threshold: 999999
+ failure_wait_time: 0
+ storage_timeout: 30
broken:
path: tmp/tests/non-existent-repositories
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
diff --git a/config/initializers/0_acts_as_taggable.rb b/config/initializers/0_acts_as_taggable.rb
index 54e9fcc31db..50dc47673ab 100644
--- a/config/initializers/0_acts_as_taggable.rb
+++ b/config/initializers/0_acts_as_taggable.rb
@@ -5,5 +5,5 @@ ActsAsTaggableOn.strict_case_match = true
ActsAsTaggableOn.tags_counter = false
# validate that counter cache is disabled
-raise "Counter cache is not disabled" if
+raise "Counter cache is not disabled" if
ActsAsTaggableOn::Tagging.reflections["tag"].options[:counter_cache]
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 5c6578d3531..38ade18bdc0 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1,3 +1,5 @@
+# rubocop:disable GitlabSecurity/PublicSend
+
require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible
class Settings < Settingslogic
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 92ce4dd03cd..f8e67ce04c9 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -37,12 +37,12 @@ def validate_storages_config
storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
end
- %w(failure_count_threshold failure_wait_time failure_reset_time storage_timeout).each do |setting|
+ %w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
# Falling back to the defaults is fine!
next if repository_storage[setting].nil?
unless repository_storage[setting].to_f > 0
- storage_validation_error("#{setting}, for storage `#{name}` needs to be greater than 0")
+ storage_validation_error("`#{setting}` for storage `#{name}` needs to be greater than 0")
end
end
end
diff --git a/config/initializers/active_record_mysql_timestamp.rb b/config/initializers/active_record_mysql_timestamp.rb
new file mode 100644
index 00000000000..af74c4ff6fb
--- /dev/null
+++ b/config/initializers/active_record_mysql_timestamp.rb
@@ -0,0 +1,30 @@
+# Make sure that MySQL won't try to use CURRENT_TIMESTAMP when the timestamp
+# column is NOT NULL. See https://gitlab.com/gitlab-org/gitlab-ce/issues/36405
+# And also: https://bugs.mysql.com/bug.php?id=75098
+# This patch was based on:
+# https://github.com/rails/rails/blob/15ef55efb591e5379486ccf53dd3e13f416564f6/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb#L34-L36
+
+if Gitlab::Database.mysql?
+ require 'active_record/connection_adapters/abstract/schema_creation'
+
+ module MySQLTimestampFix
+ def add_column_options!(sql, options)
+ # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
+ # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
+ # column to contain NULL, explicitly declare it with the NULL attribute.
+ # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
+ if sql.end_with?('timestamp') && !options[:primary_key]
+ if options[:null] != false
+ sql << ' NULL'
+ elsif options[:column].default.nil?
+ sql << ' DEFAULT 0'
+ end
+ end
+
+ super
+ end
+ end
+
+ ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
+ .prepend(MySQLTimestampFix)
+end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index 9ed96ddb0b4..943e01f1496 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,15 +1,15 @@
app = Rails.application
if app.config.serve_static_files
- # The `ActionDispatch::Static` middleware intercepts requests for static files
- # by checking if they exist in the `/public` directory.
+ # The `ActionDispatch::Static` middleware intercepts requests for static files
+ # by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same,
# except ignoring `/uploads`, letting those go through to the GitLab Rails app.
app.config.middleware.swap(
- ActionDispatch::Static,
- Gitlab::Middleware::Static,
- app.paths["public"].first,
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
app.config.static_cache_control
)
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index fc4f02453d7..0c32528311e 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -2,7 +2,7 @@
# as the ActionDispatch::Request object. This is necessary for libraries
# like rack_attack where they don't use ActionDispatch, and we want them
# to block/throttle requests on private networks.
-# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145
+# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145
module Rack
class Request
def trusted_proxy?(ip)
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 57b7c55423d..9ffdebbcff1 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -3,7 +3,7 @@
resource :repository, only: [:create] do
member do
get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
-
+
# deprecated since GitLab 9.5
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 8e1b80cd39f..6a347c2e660 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -223,6 +223,9 @@ var config = {
names: ['main', 'locale', 'common', 'webpack_runtime'],
}),
+ // enable scope hoisting
+ new webpack.optimize.ModuleConcatenationPlugin(),
+
// copy pre-compiled vendor libraries verbatim
new CopyWebpackPlugin([
{
diff --git a/db/migrate/20161020075830_default_request_access_projects.rb b/db/migrate/20161020075830_default_request_access_projects.rb
index cb790291b24..a3a53350e8d 100644
--- a/db/migrate/20161020075830_default_request_access_projects.rb
+++ b/db/migrate/20161020075830_default_request_access_projects.rb
@@ -1,7 +1,7 @@
class DefaultRequestAccessProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
-
+
def up
change_column_default :projects, :request_access_enabled, false
end
diff --git a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
index 705e11ed47d..3a4d6c4916b 100644
--- a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
+++ b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
@@ -21,7 +21,7 @@ class UpdateRetriedForCiBuild < ActiveRecord::Migration
private
def up_mysql
- # This is a trick to overcome MySQL limitation:
+ # This is a trick to overcome MySQL limitation:
# Mysql2::Error: Table 'ci_builds' is specified twice, both as a target for 'UPDATE' and as a separate source for data
# However, this leads to create a temporary table from `max(ci_builds.id)` which is slow and do full database update
execute <<-SQL.strip_heredoc
diff --git a/db/post_migrate/20170523083112_migrate_old_artifacts.rb b/db/post_migrate/20170523083112_migrate_old_artifacts.rb
index f2690bd0017..3a77b9751d3 100644
--- a/db/post_migrate/20170523083112_migrate_old_artifacts.rb
+++ b/db/post_migrate/20170523083112_migrate_old_artifacts.rb
@@ -7,7 +7,7 @@ class MigrateOldArtifacts < ActiveRecord::Migration
# This uses special heuristic to find potential candidates for data migration
# Read more about this here: https://gitlab.com/gitlab-org/gitlab-ce/issues/32036#note_30422345
-
+
def up
builds_with_artifacts.find_each do |build|
build.migrate_artifacts!
@@ -51,14 +51,14 @@ class MigrateOldArtifacts < ActiveRecord::Migration
private
def source_artifacts_path
- @source_artifacts_path ||=
+ @source_artifacts_path ||=
File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'),
ci_id.to_s, id.to_s)
end
def target_artifacts_path
- @target_artifacts_path ||=
+ @target_artifacts_path ||=
File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'),
project_id.to_s, id.to_s)
diff --git a/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb b/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb
new file mode 100644
index 00000000000..6132b553177
--- /dev/null
+++ b/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb
@@ -0,0 +1,26 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDuplicateMrEvents < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ class Event < ActiveRecord::Base
+ self.table_name = 'events'
+ end
+
+ def up
+ base_condition = "action = 1 AND target_type = 'MergeRequest' AND created_at > '2017-08-13'"
+ Event.select('target_id, count(*)')
+ .where(base_condition)
+ .group('target_id').having('count(*) > 1').each do |event|
+ duplicates = Event.where("#{base_condition} AND target_id = #{event.target_id}").pluck(:id)
+ duplicates.shift
+
+ Event.where(id: duplicates).delete_all
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3206e106552..2ea6ae29dc7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170809161910) do
+ActiveRecord::Schema.define(version: 20170815060945) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/README.md b/doc/README.md
index 4175750d497..547541c4876 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -32,6 +32,7 @@ Shortcuts to GitLab's most visited docs:
- [User documentation](user/index.md)
- [Administrator documentation](#administrator-documentation)
+- [Technical Articles](articles/index.md)
## Getting started with GitLab
diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md
new file mode 100644
index 00000000000..c64851bad2b
--- /dev/null
+++ b/doc/articles/artifactory_and_gitlab/index.md
@@ -0,0 +1,278 @@
+# How to deploy Maven projects to Artifactory with GitLab CI/CD
+
+> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Fabio Busatto](https://gitlab.com/bikebilly) ||
+> **Publication date:** 2017-08-15
+
+## Introduction
+
+In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/)
+to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://www.jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
+
+You'll create two different projects:
+
+- `simple-maven-dep`: the app built and deployed to Artifactory (available at https://gitlab.com/gitlab-examples/maven/simple-maven-dep)
+- `simple-maven-app`: the app using the previous one as a dependency (available at https://gitlab.com/gitlab-examples/maven/simple-maven-app)
+
+We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/).
+We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
+
+## Create the simple Maven dependency
+
+First of all, you need an application to work with: in this specific case we will
+use a simple one, but it could be any Maven application. This will be the
+dependency you want to package and deploy to Artifactory, in order to be
+available to other projects.
+
+### Prepare the dependency application
+
+For this article you'll use a Maven app that can be cloned from our example
+project:
+
+1. Log in to your GitLab account
+1. Create a new project by selecting **Import project from âž” Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
+ ```
+1. Click **Create project**
+
+This application is nothing more than a basic class with a stub for a JUnit based test suite.
+It exposes a method called `hello` that accepts a string as input, and prints a hello message on the screen.
+
+The project structure is really simple, and you should consider these two resources:
+
+- `pom.xml`: project object model (POM) configuration file
+- `src/main/java/com/example/dep/Dep.java`: source of our application
+
+### Configure the Artifactory deployment
+
+The application is ready to use, but you need some additional steps to deploy it to Artifactory:
+
+1. Log in to Artifactory with your user's credentials.
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel.
+1. Copy to clipboard the configuration snippet under the **Deploy** paragraph.
+1. Change the `url` value in order to have it configurable via secret variables.
+1. Copy the snippet in the `pom.xml` file for your project, just after the
+ `dependencies` section. The snippet should look like this:
+
+ ```xml
+ <distributionManagement>
+ <repository>
+ <id>central</id>
+ <name>83d43b5afeb5-releases</name>
+ <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
+ </repository>
+ </distributionManagement>
+ ```
+
+Another step you need to do before you can deploy the dependency to Artifactory
+is to configure the authentication data. It is a simple task, but Maven requires
+it to stay in a file called `settings.xml` that has to be in the `.m2` subdirectory
+in the user's homedir.
+
+Since you want to use GitLab Runner to automatically deploy the application, you
+should create the file in the project's home directory and set a command line
+parameter in `.gitlab-ci.yml` to use the custom location instead of the default one:
+
+1. Create a folder called `.m2` in the root of your repository
+1. Create a file called `settings.xml` in the `.m2` folder
+1. Copy the following content into a `settings.xml` file:
+
+ ```xml
+ <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
+ xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <servers>
+ <server>
+ <id>central</id>
+ <username>${env.MAVEN_REPO_USER}</username>
+ <password>${env.MAVEN_REPO_PASS}</password>
+ </server>
+ </servers>
+ </settings>
+ ```
+
+ Username and password will be replaced by the correct values using secret variables.
+
+### Configure GitLab CI/CD for `simple-maven-dep`
+
+Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and deploy the dependency!
+
+GitLab CI/CD uses a file in the root of the repo, named `.gitlab-ci.yml`, to read the definitions for jobs
+that will be executed by the configured GitLab Runners. You can read more about this file in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/).
+
+First of all, remember to set up secret variables for your deployment. Navigate to your project's **Settings > CI/CD** page
+and add the following secret variables (replace them with your current values, of course):
+
+- **MAVEN_REPO_URL**: `http://artifactory.example.com:8081/artifactory` (your Artifactory URL)
+- **MAVEN_REPO_USER**: `gitlab` (your Artifactory username)
+- **MAVEN_REPO_PASS**: `AKCp2WXr3G61Xjz1PLmYa3arm3yfBozPxSta4taP3SeNu2HPXYa7FhNYosnndFNNgoEds8BCS` (your Artifactory Encrypted Password)
+
+Now it's time to define jobs in `.gitlab-ci.yml` and push it to the repo:
+
+```yaml
+image: maven:latest
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+deploy:
+ stage: deploy
+ script:
+ - mvn $MAVEN_CLI_OPTS deploy
+ only:
+ - master
+```
+
+GitLab Runner will use the latest [Maven Docker image](https://hub.docker.com/_/maven/), which already contains all the tools and the dependencies you need to manage the project,
+in order to run the jobs.
+
+Environment variables are set to instruct Maven to use the `homedir` of the repo instead of the user's home when searching for configuration and dependencies.
+
+Caching the `.m2/repository folder` (where all the Maven files are stored), and the `target` folder (where our application will be created), is useful for speeding up the process
+by running all Maven phases in a sequential order, therefore, executing `mvn test` will automatically run `mvn compile` if necessary.
+
+Both `build` and `test` jobs leverage the `mvn` command to compile the application and to test it as defined in the test suite that is part of the application.
+
+Deploy to Artifactory is done as defined by the secret variables we have just set up.
+The deployment occurs only if we're pushing or merging to `master` branch, so that the development versions are tested but not published.
+
+Done! Now you have all the changes in the GitLab repo, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
+If the deployment has been successful, the deploy job log will output:
+
+```
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 1.983 s
+```
+
+>**Note**:
+the `mvn` command downloads a lot of files from the internet, so you'll see a lot of extra activity in the log the first time you run it.
+
+Yay! You did it! Checking in Artifactory will confirm that you have a new artifact available in the `libs-release-local` repo.
+
+## Create the main Maven application
+
+Now that you have the dependency available on Artifactory, it's time to use it!
+Let's see how we can have it as a dependency to our main application.
+
+### Prepare the main application
+
+We'll use again a Maven app that can be cloned from our example project:
+
+1. Create a new project by selecting **Import project from âž” Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
+ ```
+1. Click **Create project**
+
+This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
+file you can see that it imports the `com.example.dep.Dep` class and calls the `hello` method passing `GitLab` as a parameter.
+
+Since Maven doesn't know how to resolve the dependency, you need to modify the configuration:
+
+1. Go back to Artifactory
+1. Browse the `libs-release-local` repository
+1. Select the `simple-maven-dep-1.0.jar` file
+1. Find the configuration snippet from the **Dependency Declaration** section of the main panel
+1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
+ The snippet should look like this:
+
+ ```xml
+ <dependency>
+ <groupId>com.example.dep</groupId>
+ <artifactId>simple-maven-dep</artifactId>
+ <version>1.0</version>
+ </dependency>
+ ```
+
+### Configure the Artifactory repository location
+
+At this point you defined the dependency for the application, but you still miss where you can find the required files.
+You need to create a `.m2/settings.xml` file as you did for the dependency project, and let Maven know the location using environment variables.
+
+Here is how you can get the content of the file directly from Artifactory:
+
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel
+1. Click on **Generate Maven Settings**
+1. Click on **Generate Settings**
+1. Copy to clipboard the configuration file
+1. Save the file as `.m2/settings.xml` in your repo
+
+Now you are ready to use the Artifactory repository to resolve dependencies and use `simple-maven-dep` in your main application!
+
+### Configure GitLab CI/CD for `simple-maven-app`
+
+You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
+
+You want to leverage [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and run your awesome application,
+and see if you can get the greeting as expected!
+
+All you need to do is to add the following `.gitlab-ci.yml` to the repo:
+
+```yaml
+image: maven:latest
+
+stages:
+ - build
+ - test
+ - run
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+run:
+ stage: run
+ script:
+ - mvn $MAVEN_CLI_OPTS package
+ - mvn $MAVEN_CLI_OPTS exec:java -Dexec.mainClass="com.example.app.App"
+```
+
+It is very similar to the configuration used for `simple-maven-dep`, but instead of the `deploy` job there is a `run` job.
+Probably something that you don't want to use in real projects, but here it is useful to see the application executed automatically.
+
+And that's it! In the `run` job output log you will find a friendly hello to GitLab!
+
+## Conclusion
+
+In this article we covered the basic steps to use an Artifactory Maven repository to automatically publish and consume artifacts.
+
+A similar approach could be used to interact with any other Maven compatible Binary Repository Manager.
+Obviously, you can improve these examples, optimizing the `.gitlab-ci.yml` file to better suit your needs, and adapting to your workflow.
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index 130e8f542b4..25a24bc1d32 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** admin guide ||
> **Level:** intermediary ||
> **Author:** [Chris Wilson](https://gitlab.com/MrChrisW) ||
-> **Publication date:** 2017/05/03
+> **Publication date:** 2017-05-03
## Introduction
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
index 66d866b2d09..37b60501ce2 100644
--- a/doc/articles/how_to_install_git/index.md
+++ b/doc/articles/how_to_install_git/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** user guide ||
> **Level:** beginner ||
> **Author:** [Sean Packham](https://gitlab.com/SeanPackham) ||
-> **Publication date:** 2017/05/15
+> **Publication date:** 2017-05-15
To begin contributing to GitLab projects
you will need to install the Git client on your computer.
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 558c624fe39..3039faca411 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -26,6 +26,7 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
+| [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017/07/11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017/07/27 |
diff --git a/doc/articles/openshift_and_gitlab/index.md b/doc/articles/openshift_and_gitlab/index.md
index 7f76e577efa..c0bbcfe2a8a 100644
--- a/doc/articles/openshift_and_gitlab/index.md
+++ b/doc/articles/openshift_and_gitlab/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial ||
> **Level:** intermediary ||
> **Author:** [Achilleas Pipinellis](https://gitlab.com/axil) ||
-> **Publication date:** 2016/06/28
+> **Publication date:** 2016-06-28
## Introduction
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 10ea9467942..c722d895f42 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -111,7 +111,8 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Phoenix](examples/test-phoenix-application.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
-- **Blog posts**
+- **Articles**
+ - [How to deploy Maven projects to Artifactory with GitLab CI/CD](../articles/artifactory_and_gitlab/index.md)
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
diff --git a/doc/development/README.md b/doc/development/README.md
index 58993c52dcd..dd150421b65 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -46,6 +46,7 @@
## Databases
+- [Merge Request Checklist](database_merge_request_checklist.md)
- [What requires downtime?](what_requires_downtime.md)
- [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md)
@@ -56,6 +57,9 @@
- [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
- [Iterating Tables In Batches](iterating_tables_in_batches.md)
+- [Ordering Table Columns](ordering_table_columns.md)
+- [Verifying Database Capabilities](verifying_database_capabilities.md)
+- [Hash Indexes](hash_indexes.md)
## i18n
diff --git a/doc/development/database_merge_request_checklist.md b/doc/development/database_merge_request_checklist.md
new file mode 100644
index 00000000000..75c395b61ef
--- /dev/null
+++ b/doc/development/database_merge_request_checklist.md
@@ -0,0 +1,15 @@
+# Merge Request Checklist
+
+When creating a merge request that performs database related changes (schema
+changes, adjusting queries to optimise performance, etc) you should use the
+merge request template called "Database Changes". This template contains a
+checklist of steps to follow to make sure the changes are up to snuff.
+
+To use the checklist, create a new merge request and click on the "Choose a
+template" dropdown, then click "Database Changes".
+
+An example of this checklist can be found at
+https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12463.
+
+The source code of the checklist can be found in at
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20Changes.md
diff --git a/doc/development/hash_indexes.md b/doc/development/hash_indexes.md
new file mode 100644
index 00000000000..e6c1b3590b1
--- /dev/null
+++ b/doc/development/hash_indexes.md
@@ -0,0 +1,20 @@
+# Hash Indexes
+
+Both PostgreSQL and MySQL support hash indexes besides the regular btree
+indexes. Hash indexes however are to be avoided at all costs. While they may
+_sometimes_ provide better performance the cost of rehashing can be very high.
+More importantly: at least until PostgreSQL 10.0 hash indexes are not
+WAL-logged, meaning they are not replicated to any replicas. From the PostgreSQL
+documentation:
+
+> Hash index operations are not presently WAL-logged, so hash indexes might need
+> to be rebuilt with REINDEX after a database crash if there were unwritten
+> changes. Also, changes to hash indexes are not replicated over streaming or
+> file-based replication after the initial base backup, so they give wrong
+> answers to queries that subsequently use them. For these reasons, hash index
+> use is presently discouraged.
+
+RuboCop is configured to register an offence when it detects the use of a hash
+index.
+
+Instead of using hash indexes you should use regular btree indexes.
diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md
new file mode 100644
index 00000000000..249e70c7b0e
--- /dev/null
+++ b/doc/development/ordering_table_columns.md
@@ -0,0 +1,127 @@
+# Ordering Table Columns
+
+Similar to C structures the space of a table is influenced by the order of
+columns. This is because the size of columns is aligned depending on the type of
+the column. Take the following column order for example:
+
+* id (integer, 4 bytes)
+* name (text, variable)
+* user_id (integer, 4 bytes)
+
+Integers are aligned to the word size. This means that on a 64 bit platform the
+actual size of each column would be: 8 bytes, variable, 8 bytes. This means that
+each row will require at least 16 bytes for the two integers, and a variable
+amount for the text field. If a table has a few rows this is not an issue, but
+once you start storing millions of rows you can save space by using a different
+order. For the above example a more ideal column order would be the following:
+
+* id (integer, 4 bytes)
+* user_id (integer, 4 bytes)
+* name (text, variable)
+
+In this setup the `id` and `user_id` columns can be packed together, which means
+we only need 8 bytes to store _both_ of them. This in turn each row will require
+8 bytes less of space.
+
+For GitLab we require that columns of new tables are ordered based to use the
+least amount of space. An easy way of doing this is to order them based on the
+type size in descending order with variable sizes (string and text columns for
+example) at the end.
+
+## Type Sizes
+
+While the PostgreSQL docuemntation
+(https://www.postgresql.org/docs/current/static/datatype.html) contains plenty
+of information we will list the sizes of common types here so it's easier to
+look them up. Here "word" refers to the word size, which is 4 bytes for a 32
+bits platform and 8 bytes for a 64 bits platform.
+
+| Type | Size | Aligned To |
+|:-----------------|:-------------------------------------|:-----------|
+| smallint | 2 bytes | 1 word |
+| integer | 4 bytes | 1 word |
+| bigint | 8 bytes | 8 bytes |
+| real | 4 bytes | 1 word |
+| double precision | 8 bytes | 8 bytes |
+| boolean | 1 byte | not needed |
+| text / string | variable, 1 byte plus the data | 1 word |
+| bytea | variable, 1 or 4 bytes plus the data | 1 word |
+| timestamp | 8 bytes | 8 bytes |
+| timestamptz | 8 bytes | 8 bytes |
+| date | 4 bytes | 1 word |
+
+A "variable" size means the actual size depends on the value being stored. If
+PostgreSQL determines this can be embedded directly into a row it may do so, but
+for very large values it will store the data externally and store a pointer (of
+1 word in size) in the column. Because of this variable sized columns should
+always be at the end of a table.
+
+## Real Example
+
+Let's use the "events" table as an example, which currently has the following
+layout:
+
+| Column | Type | Size |
+|:------------|:----------------------------|:---------|
+| id | integer | 4 bytes |
+| target_type | character varying | variable |
+| target_id | integer | 4 bytes |
+| title | character varying | variable |
+| data | text | variable |
+| project_id | integer | 4 bytes |
+| created_at | timestamp without time zone | 8 bytes |
+| updated_at | timestamp without time zone | 8 bytes |
+| action | integer | 4 bytes |
+| author_id | integer | 4 bytes |
+
+After adding padding to align the columns this would translate to columns being
+divided into fixed size chunks as follows:
+
+| Chunk Size | Columns |
+|:-----------|:------------------|
+| 8 bytes | id |
+| variable | target_type |
+| 8 bytes | target_id |
+| variable | title |
+| variable | data |
+| 8 bytes | project_id |
+| 8 bytes | created_at |
+| 8 bytes | updated_at |
+| 8 bytes | action, author_id |
+
+This means that excluding the variable sized data we need at least 48 bytes per
+row.
+
+We can optimise this by using the following column order instead:
+
+| Column | Type | Size |
+|:------------|:----------------------------|:---------|
+| created_at | timestamp without time zone | 8 bytes |
+| updated_at | timestamp without time zone | 8 bytes |
+| id | integer | 4 bytes |
+| target_id | integer | 4 bytes |
+| project_id | integer | 4 bytes |
+| action | integer | 4 bytes |
+| author_id | integer | 4 bytes |
+| target_type | character varying | variable |
+| title | character varying | variable |
+| data | text | variable |
+
+This would produce the following chunks:
+
+| Chunk Size | Columns |
+|:-----------|:-------------------|
+| 8 bytes | created_at |
+| 8 bytes | updated_at |
+| 8 bytes | id, target_id |
+| 8 bytes | project_id, action |
+| 8 bytes | author_id |
+| variable | target_type |
+| variable | title |
+| variable | data |
+
+Here we only need 40 bytes per row excluding the variable sized data. 8 bytes
+being saved may not sound like much, but for tables as large as the "events"
+table it does begin to matter. For example, when storing 80 000 000 rows this
+translates to a space saving of at least 610 MB: all by just changing the order
+of a few columns.
diff --git a/doc/development/serializing_data.md b/doc/development/serializing_data.md
index 2b56f48bc44..37332c20147 100644
--- a/doc/development/serializing_data.md
+++ b/doc/development/serializing_data.md
@@ -1,7 +1,8 @@
# Serializing Data
**Summary:** don't store serialized data in the database, use separate columns
-and/or tables instead.
+and/or tables instead. This includes storing of comma separated values as a
+string.
Rails makes it possible to store serialized data in JSON, YAML or other formats.
Such a field can be defined as follows:
diff --git a/doc/development/sql.md b/doc/development/sql.md
index 23fd7604957..974b1d99dff 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -216,4 +216,30 @@ exact same results. This also means there's no need to add an index on
`created_at` to ensure consistent performance as `id` is already indexed by
default.
+## Use WHERE EXISTS instead of WHERE IN
+
+While `WHERE IN` and `WHERE EXISTS` can be used to produce the same data it is
+recommended to use `WHERE EXISTS` whenever possible. While in many cases
+PostgreSQL can optimise `WHERE IN` quite well there are also many cases where
+`WHERE EXISTS` will perform (much) better.
+
+In Rails you have to use this by creating SQL fragments:
+
+```ruby
+Project.where('EXISTS (?)', User.select(1).where('projects.creator_id = users.id AND users.foo = X'))
+```
+
+This would then produce a query along the lines of the following:
+
+```sql
+SELECT *
+FROM projects
+WHERE EXISTS (
+ SELECT 1
+ FROM users
+ WHERE projects.creator_id = users.id
+ AND users.foo = X
+)
+```
+
[gin-index]: http://www.postgresql.org/docs/current/static/gin.html
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
new file mode 100644
index 00000000000..cc6d62957e3
--- /dev/null
+++ b/doc/development/verifying_database_capabilities.md
@@ -0,0 +1,26 @@
+# Verifying Database Capabilities
+
+Sometimes certain bits of code may only work on a certain database and/or
+version. While we try to avoid such code as much as possible sometimes it is
+necessary to add database (version) specific behaviour.
+
+To facilitate this we have the following methods that you can use:
+
+* `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
+* `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
+* `Gitlab::Database.version`: returns the PostgreSQL version number as a string
+ in the format `X.Y.Z`. This method does not work for MySQL
+
+This allows you to write code such as:
+
+```ruby
+if Gitlab::Database.postgresql?
+ if Gitlab::Database.version.to_f >= 9.6
+ run_really_fast_query
+ else
+ run_fast_query
+ end
+else
+ run_query
+end
+```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index b14cb2d44c4..66eb7675896 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
- # Make sure Git is version 2.8.4 or higher
+ # Make sure Git is version 2.13.0 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 555b0cf77ea..dcf210e1085 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -5,17 +5,17 @@ particular group or project. If a user is both in a group's project and the
project itself, the highest permission level is used.
On public and internal projects the Guest role is not enforced. All users will
-be able to create issues, leave comments, and pull or download the project code.
+be able to create issues, leave comments, and clone or download the project code.
-When a member leaves the team the all assigned Issues and Merge Requests
+When a member leaves the team all the assigned [Issues](project/issues/index.md) and [Merge Requests](project/merge_requests/index.md)
will be unassigned automatically.
-GitLab administrators receive all permissions.
+GitLab [administrators](../README.md#administrator-documentation) receive all permissions.
To add or import a user, you can follow the
[project members documentation](../user/project/members/index.md).
-## Project
+## Project members permissions
The following table depicts the various user permission levels in a project.
@@ -75,7 +75,58 @@ The following table depicts the various user permission levels in a project.
| Remove protected branches [^3] | | | | | |
| Remove pages | | | | | ✓ |
-## Group
+## Project features permissions
+
+### Wiki and issues
+
+Project features like wiki and issues can be hidden from users depending on
+which visibility level you select on project settings.
+
+- Disabled: disabled for everyone
+- Only team members: only team members will see even if your project is public or internal
+- Everyone with access: everyone can see depending on your project visibility level
+
+### Protected branches
+
+To prevent people from messing with history or pushing code without
+review, we've created protected branches. Read through the documentation on
+[protected branches](project/protected_branches.md)
+to learn more.
+
+Additionally, you can allow or forbid users with Master and/or
+Developer permissions to push to a protected branch. Read through the documentation on
+[Allowed to Merge and Allowed to Push settings](project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
+to learn more.
+
+### Cycle Analytics permissions
+
+Find the current permissions on the Cycle Analytics dashboard on
+the [documentation on Cycle Analytics permissions](project/cycle_analytics.md#permissions).
+
+### Issue Board permissions
+
+Developers and users with higher permission level can use all
+the functionality of the Issue Board, that is create/delete lists
+and drag issues around. Read though the
+[documentation on Issue Boards permissions](project/issue_board.md#permissions)
+to learn more.
+
+### File Locking permissions (EEP)
+
+The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
+
+Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more.
+
+File Locking is available in
+[GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) only.
+
+### Confidential Issues permissions
+
+Confidential issues can be accessed by reporters and higher permission levels,
+as well as by guest users that create a confidential issue. To learn more,
+read through the documentation on [permissions and access to confidential issues](project/issues/confidential_issues.md#permissions-and-access-to-confidential-issues).
+
+## Group members permissions
Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
@@ -91,7 +142,16 @@ group.
| Remove group | | | | | ✓ |
| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-## External Users
+### Subgroup permissions
+
+When you add a member to a subgroup, they inherit the membership and
+permission level from the parent group. This model allows access to
+nested groups if you have membership in one of its parents.
+
+To learn more, read through the documentation on
+[subgroups memberships](group/subgroups/index.md#membership).
+
+## External users permissions
In cases where it is desired that a user has access only to some internal or
private projects, there is the option of creating **External Users**. This
@@ -115,18 +175,9 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
-## Project features
-
-Project features like wiki and issues can be hidden from users depending on
-which visibility level you select on project settings.
-
-- Disabled: disabled for everyone
-- Only team members: only team members will see even if your project is public or internal
-- Everyone with access: everyone can see depending on your project visibility level
-
-## GitLab CI
+## GitLab CI/CD permissions
-GitLab CI permissions rely on the role the user has in GitLab. There are four
+GitLab CI/CD permissions rely on the role the user has in GitLab. There are four
permission levels in total:
- admin
@@ -134,7 +185,7 @@ permission levels in total:
- developer
- guest/reporter
-The admin user can perform any action on GitLab CI in scope of the GitLab
+The admin user can perform any action on GitLab CI/CD in scope of the GitLab
instance and project. In addition, all admins can use the admin interface under
`/admin/runners`.
@@ -150,7 +201,7 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
-### Jobs permissions
+### Job permissions
>**Note:**
GitLab 8.12 has a completely redesigned job permissions system.
@@ -174,6 +225,26 @@ users:
| Push container images to current project | | ✓ | ✓ | ✓ |
| Push container images to other projects | | | | |
+### New CI job permissions model
+
+GitLab 8.12 has a completely redesigned job permissions system. To learn more,
+read through the documentation on the [new CI/CD permissions model](project/new_ci_build_permissions_model.md#new-ci-job-permissions-model).
+
+## LDAP users permissions
+
+Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
+Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/index.html#updating-user-permissions-new-feature) to learn more.
+
+## Auditor users permissions (EEP)
+
+An Auditor user should be able to access all projects and groups of a GitLab instance
+with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user).
+
+Auditor users are available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/)
+only.
+
+----
+
[^1]: Guest users can only view the confidential issues they created themselves
[^2]: If **Public pipelines** is enabled in **Project Settings > Pipelines**
[^3]: Not allowed for Guest, Reporter, Developer, Master, or Owner
diff --git a/doc/user/search/img/group_issues_filter.png b/doc/user/search/img/group_issues_filter.png
new file mode 100644
index 00000000000..45eced79b99
--- /dev/null
+++ b/doc/user/search/img/group_issues_filter.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 6d59dcc6c75..79f34fd29ba 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,14 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+## Issues and merge requests per group
+
+Similar to **Issues and merge requests per project**, you can also search for issues
+within a group. Navigate to a group's **Issues** tab and query search results in
+the same way as you do for projects.
+
+![filter issues in a group](img/group_issues_filter.png)
+
## Search history
You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser.
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
index 10ebe705365..4f44f932a6d 100644
--- a/features/steps/profile/emails.rb
+++ b/features/steps/profile/emails.rb
@@ -28,7 +28,7 @@ class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
expect(email).to be_nil
expect(page).not_to have_content("my@email.com")
end
-
+
step 'I click link "Remove" for "my@email.com"' do
# there should only be one remove button at this time
click_link "Remove"
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 0d2d71e336a..c4c0fdda665 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -122,7 +122,7 @@ module API
error_classes = [MissingTokenError, TokenNotFoundError,
ExpiredError, RevokedError, InsufficientScopeError]
- base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
end
def oauth2_bearer_token_error_handler
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 18cd604a216..e8dd61e493f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -83,7 +83,7 @@ module API
expose :created_at, :last_activity_at
end
- class Project < BasicProjectDetails
+ class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
expose :_links do
@@ -541,8 +541,9 @@ module API
target_url = "namespace_project_#{target_type}_url"
target_anchor = "note_#{todo.note_id}" if todo.note_id?
- Gitlab::Routing.url_helpers.public_send(target_url,
- todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
+ Gitlab::Routing
+ .url_helpers
+ .public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend
end
expose :body
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 450334fee84..e2ac7142bc4 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,5 +1,8 @@
module API
class Files < Grape::API
+ # Prevents returning plain/text responses for files with .txt extension
+ after_validation { content_type "application/json" }
+
helpers do
def commit_params(attrs)
{
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index d742f2e18d0..dccf4fa27a7 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -61,7 +61,7 @@ module API
service_args = [user_project, current_user, protected_branch_params]
protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute
-
+
if protected_branch.persisted?
present protected_branch, with: Entities::ProtectedBranch, project: user_project
else
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 5bf5a18e42f..31f940fe96b 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -153,7 +153,7 @@ module API
render_api_error!('Scope contains invalid value', 400)
end
- runners.send(scope)
+ runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
end
def get_runner(id)
diff --git a/lib/api/v3/notes.rb b/lib/api/v3/notes.rb
index 23fe95e42e4..d49772b92f2 100644
--- a/lib/api/v3/notes.rb
+++ b/lib/api/v3/notes.rb
@@ -22,7 +22,7 @@ module API
use :pagination
end
get ":id/#{noteables_str}/:noteable_id/notes" do
- noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
# We exclude notes that are cross-references and that cannot be viewed
@@ -50,7 +50,7 @@ module API
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
- noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
note = noteable.notes.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
@@ -76,7 +76,7 @@ module API
noteable_id: params[:noteable_id]
}
- noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
+ noteable = user_project.public_send(noteables_str.to_sym).find(params[:noteable_id]) # rubocop:disable GitlabSecurity/PublicSend
if can?(current_user, noteable_read_ability_name(noteable), noteable)
if params[:created_at] && (current_user.admin? || user_project.owner == current_user)
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index 53a229256a5..ed01a72ff9f 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -95,10 +95,10 @@ module Banzai
private
def external_issues_cached(attribute)
- return project.public_send(attribute) unless RequestStore.active?
+ return project.public_send(attribute) unless RequestStore.active? # rubocop:disable GitlabSecurity/PublicSend
cached_attributes = RequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
- cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil?
+ cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend
cached_attributes[project.id][attribute]
end
end
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
index 7a81d583b82..bcb4f332267 100644
--- a/lib/banzai/filter/image_lazy_load_filter.rb
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -6,9 +6,9 @@ module Banzai
doc.xpath('descendant-or-self::img').each do |img|
img['class'] ||= '' << 'lazy'
img['data-src'] = img['src']
- img['src'] = LazyImageTagHelper.placeholder_image
+ img['src'] = LazyImageTagHelper.placeholder_image
end
-
+
doc
end
end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 002a3341ccd..2196a92474c 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -37,7 +37,7 @@ module Banzai
objects.each_with_index do |object, index|
redacted_data = redacted[index]
- object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe)
+ object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) # rubocop:disable GitlabSecurity/PublicSend
object.user_visible_reference_count = redacted_data[:visible_reference_count]
end
end
diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb
index 321fd5bbe14..3ae3bed570d 100644
--- a/lib/banzai/pipeline/base_pipeline.rb
+++ b/lib/banzai/pipeline/base_pipeline.rb
@@ -18,7 +18,7 @@ module Banzai
define_method(meth) do |text, context|
context = transform_context(context)
- html_pipeline.send(meth, text, context)
+ html_pipeline.__send__(meth, text, context) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index ad08c0905e2..95d82d17658 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -43,7 +43,7 @@ module Banzai
# Same as +render_field+, but without consulting or updating the cache field
def self.cacheless_render_field(object, field, options = {})
- text = object.__send__(field)
+ text = object.__send__(field) # rubocop:disable GitlabSecurity/PublicSend
context = object.banzai_render_context(field).merge(options)
cacheless_render(text, context)
@@ -156,7 +156,7 @@ module Banzai
# method.
def self.full_cache_multi_key(cache_key, pipeline_name)
return unless cache_key
- Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
+ Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend
end
# GitLab EE needs to disable updates on GET requests in Geo
diff --git a/lib/bitbucket/collection.rb b/lib/bitbucket/collection.rb
index 3a9379ff680..a78495dbf5e 100644
--- a/lib/bitbucket/collection.rb
+++ b/lib/bitbucket/collection.rb
@@ -13,7 +13,7 @@ module Bitbucket
def method_missing(method, *args)
return super unless self.respond_to?(method)
- self.send(method, *args) do |item|
+ self.__send__(method, *args) do |item| # rubocop:disable GitlabSecurity/PublicSend
block_given? ? yield(item) : item
end
end
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index 8354fc8d595..b9e9f9f7f4a 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -208,7 +208,7 @@ module Ci
return unless command = stack.shift()
if self.respond_to?("on_#{command}", true)
- self.send("on_#{command}", stack)
+ self.__send__("on_#{command}", stack) # rubocop:disable GitlabSecurity/PublicSend
end
evaluate_command_stack(stack)
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index fd7b97d3167..5bef29eb1da 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -7,7 +7,7 @@ class ProjectUrlConstrainer
return false unless DynamicPathValidator.valid_project_path?(full_path)
# We intentionally allow SELECT(*) here so result of this query can be used
- # as cache for further Project.find_by_full_path calls within request
+ # as cache for further Project.find_by_full_path calls within request
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
index df94cafb6a1..b028169f500 100644
--- a/lib/declarative_policy/base.rb
+++ b/lib/declarative_policy/base.rb
@@ -109,7 +109,7 @@ module DeclarativePolicy
name = name.to_sym
if delegation_block.nil?
- delegation_block = proc { @subject.__send__(name) }
+ delegation_block = proc { @subject.__send__(name) } # rubocop:disable GitlabSecurity/PublicSend
end
own_delegations[name] = delegation_block
@@ -221,7 +221,7 @@ module DeclarativePolicy
end
# computes the given ability and prints a helpful debugging output
- # showing which
+ # showing which
def debug(ability, *a)
runner(ability).debug(*a)
end
diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb
index b26807a7622..6ba1e7a3c5c 100644
--- a/lib/declarative_policy/dsl.rb
+++ b/lib/declarative_policy/dsl.rb
@@ -93,7 +93,7 @@ module DeclarativePolicy
def method_missing(m, *a, &b)
return super unless @context_class.respond_to?(m)
- @context_class.__send__(m, *a, &b)
+ @context_class.__send__(m, *a, &b) # rubocop:disable GitlabSecurity/PublicSend
end
def respond_to_missing?(m)
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
index eb19ab45ac3..de391de9059 100644
--- a/lib/file_size_validator.rb
+++ b/lib/file_size_validator.rb
@@ -44,13 +44,13 @@ class FileSizeValidator < ActiveModel::EachValidator
when Integer
check_value
when Symbol
- record.send(check_value)
+ record.public_send(check_value) # rubocop:disable GitlabSecurity/PublicSend
end
value ||= [] if key == :maximum
value_size = value.size
- next if value_size.send(validity_check, check_value)
+ next if value_size.public_send(validity_check, check_value) # rubocop:disable GitlabSecurity/PublicSend
errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:file_size] = help.number_to_human_size check_value
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 7d3aa532750..8cb4060cd97 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -101,7 +101,7 @@ module Gitlab
if Service.available_services_names.include?(underscored_service)
# We treat underscored_service as a trusted input because it is included
# in the Service.available_services_names whitelist.
- service = project.public_send("#{underscored_service}_service")
+ service = project.public_send("#{underscored_service}_service") # rubocop:disable GitlabSecurity/PublicSend
if service && service.activated? && service.valid_token?(password)
Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
@@ -149,7 +149,7 @@ module Gitlab
def abilities_for_scope(scopes)
scopes.map do |scope|
- self.public_send(:"#{scope}_scope_authentication_abilities")
+ self.public_send(:"#{scope}_scope_authentication_abilities") # rubocop:disable GitlabSecurity/PublicSend
end.flatten.uniq
end
diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb
index 1089bc9f89e..e6173d45af3 100644
--- a/lib/gitlab/auth/ip_rate_limiter.rb
+++ b/lib/gitlab/auth/ip_rate_limiter.rb
@@ -11,11 +11,11 @@ module Gitlab
def enabled?
config.enabled
end
-
+
def reset!
Rack::Attack::Allow2Ban.reset(ip, config)
end
-
+
def register_fail!
# Allow2Ban.filter will return false if this IP has not failed too often yet
@banned = Rack::Attack::Allow2Ban.filter(ip, config) do
@@ -23,17 +23,17 @@ module Gitlab
ip_can_be_banned?
end
end
-
+
def banned?
@banned
end
-
+
private
-
+
def config
Gitlab.config.rack_attack.git_basic_auth
end
-
+
def ip_can_be_banned?
config.ip_whitelist.exclude?(ip)
end
diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb
index f1a04affd38..754a45c3257 100644
--- a/lib/gitlab/cache/request_cache.rb
+++ b/lib/gitlab/cache/request_cache.rb
@@ -69,7 +69,7 @@ module Gitlab
instance_variable_set(ivar_name, {})
end
- key = __send__(cache_key_method_name, args)
+ key = __send__(cache_key_method_name, args) # rubocop:disable GitlabSecurity/PublicSend
store.fetch(key) { store[key] = super(*args) }
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index a375ccbece0..a788fb3fcbc 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -60,7 +60,7 @@ module Gitlab
begin
path = read_string(gz).force_encoding('UTF-8')
meta = read_string(gz).force_encoding('UTF-8')
-
+
next unless path.valid_encoding? && meta.valid_encoding?
next unless path =~ match_pattern
next if path =~ INVALID_PATH_PATTERN
diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb
index 576a761423e..cf71d47df8e 100644
--- a/lib/gitlab/diff/line_mapper.rb
+++ b/lib/gitlab/diff/line_mapper.rb
@@ -38,7 +38,7 @@ module Gitlab
# - The first diff line with a higher line number, if it falls between diff contexts
# - The last known diff line, if it falls after the last diff context
diff_line = diff_lines.find do |diff_line|
- diff_from_line = diff_line.send(from)
+ diff_from_line = diff_line.public_send(from) # rubocop:disable GitlabSecurity/PublicSend
diff_from_line && diff_from_line >= from_line
end
diff_line ||= diff_lines.last
@@ -47,8 +47,8 @@ module Gitlab
# mapped line number is the same as the specified line number.
return from_line unless diff_line
- diff_from_line = diff_line.send(from)
- diff_to_line = diff_line.send(to)
+ diff_from_line = diff_line.public_send(from) # rubocop:disable GitlabSecurity/PublicSend
+ diff_to_line = diff_line.public_send(to) # rubocop:disable GitlabSecurity/PublicSend
# If the line was removed, there is no mapped line number.
return unless diff_to_line
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 77b81d2d437..7780f4e4d4f 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -54,7 +54,7 @@ module Gitlab
# [[commit_sha, path], [commit_sha, path], ...]. If blob_size_limit < 0 then the
# full blob contents are returned. If blob_size_limit >= 0 then each blob will
# contain no more than limit bytes in its data attribute.
- #
+ #
# Keep in mind that this method may allocate a lot of memory. It is up
# to the caller to limit the number of blobs and blob_size_limit.
#
@@ -173,7 +173,7 @@ module Gitlab
def initialize(options)
%w(id name path size data mode commit_id binary).each do |key|
- self.send("#{key}=", options[key.to_sym])
+ self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
@loaded_all_data = false
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 38772d06dbd..1d5ca68137a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -18,6 +18,28 @@ module Gitlab
InvalidBlobName = Class.new(StandardError)
InvalidRef = Class.new(StandardError)
+ class << self
+ # Unlike `new`, `create` takes the storage path, not the storage name
+ def create(storage_path, name, bare: true, symlink_hooks_to: nil)
+ repo_path = File.join(storage_path, name)
+ repo_path += '.git' unless repo_path.end_with?('.git')
+
+ FileUtils.mkdir_p(repo_path, mode: 0770)
+
+ # Equivalent to `git --git-path=#{repo_path} init [--bare]`
+ repo = Rugged::Repository.init_at(repo_path, bare)
+ repo.close
+
+ if symlink_hooks_to.present?
+ hooks_path = File.join(repo_path, 'hooks')
+ FileUtils.rm_rf(hooks_path)
+ FileUtils.ln_s(symlink_hooks_to, hooks_path)
+ end
+
+ true
+ end
+ end
+
# Full path to repo
attr_reader :path
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index 8e959c57c7c..b54962a4456 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -89,7 +89,7 @@ module Gitlab
def initialize(options)
%w(id root_id name path type mode commit_id).each do |key|
- self.send("#{key}=", options[key.to_sym])
+ self.send("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 70177cd0fec..9a5f4f598b2 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -55,7 +55,7 @@ module Gitlab
def self.call(storage, service, rpc, request)
metadata = request_metadata(storage)
metadata = yield(metadata) if block_given?
- stub(service, storage).send(rpc, request, metadata)
+ stub(service, storage).__send__(rpc, request, metadata) # rubocop:disable GitlabSecurity/PublicSend
end
def self.request_metadata(storage)
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
index 8c80791e7c9..f330041cc00 100644
--- a/lib/gitlab/github_import/base_formatter.rb
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -11,7 +11,9 @@ module Gitlab
end
def create!
- project.public_send(project_association).find_or_create_by!(find_condition) do |record|
+ association = project.public_send(project_association) # rubocop:disable GitlabSecurity/PublicSend
+
+ association.find_or_create_by!(find_condition) do |record|
record.attributes = attributes
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 7dbeec5b010..0550f9695bd 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -120,7 +120,7 @@ module Gitlab
def request(method, *args, &block)
sleep rate_limit_sleep_time if rate_limit_exceed?
- data = api.send(method, *args)
+ data = api.__send__(method, *args) # rubocop:disable GitlabSecurity/PublicSend
return data unless data.is_a?(Array)
last_response = api.last_response
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 266b1a6fece..373062b354b 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -289,7 +289,7 @@ module Gitlab
opts.last[:page] = current_page(resource_type)
- client.public_send(resource_type, *opts) do |resources|
+ client.public_send(resource_type, *opts) do |resources| # rubocop:disable GitlabSecurity/PublicSend
yield resources
increment_page(resource_type)
end
diff --git a/lib/gitlab/import_export/attributes_finder.rb b/lib/gitlab/import_export/attributes_finder.rb
index d230de781d5..56042ddecbf 100644
--- a/lib/gitlab/import_export/attributes_finder.rb
+++ b/lib/gitlab/import_export/attributes_finder.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class AttributesFinder
-
def initialize(included_attributes:, excluded_attributes:, methods:)
@included_attributes = included_attributes || {}
@excluded_attributes = excluded_attributes || {}
diff --git a/lib/gitlab/lazy.rb b/lib/gitlab/lazy.rb
index 2a659ae4c74..99594577141 100644
--- a/lib/gitlab/lazy.rb
+++ b/lib/gitlab/lazy.rb
@@ -16,7 +16,7 @@ module Gitlab
def method_missing(name, *args, &block)
__evaluate__
- @result.__send__(name, *args, &block)
+ @result.__send__(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
def respond_to_missing?(name, include_private = false)
diff --git a/lib/gitlab/ldap/auth_hash.rb b/lib/gitlab/ldap/auth_hash.rb
index 95378e5a769..4fbc5fa5262 100644
--- a/lib/gitlab/ldap/auth_hash.rb
+++ b/lib/gitlab/ldap/auth_hash.rb
@@ -17,7 +17,7 @@ module Gitlab
value = value.first if value
break if value.present?
end
-
+
return super unless value
Gitlab::Utils.force_utf8(value)
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
index 43eb73250b7..e138b466a34 100644
--- a/lib/gitlab/ldap/person.rb
+++ b/lib/gitlab/ldap/person.rb
@@ -32,7 +32,7 @@ module Gitlab
end
def uid
- entry.send(config.uid).first
+ entry.public_send(config.uid).first # rubocop:disable GitlabSecurity/PublicSend
end
def username
@@ -65,7 +65,7 @@ module Gitlab
return nil unless selected_attr
- entry.public_send(selected_attr)
+ entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb
index 699d8b9fc07..306923902e0 100644
--- a/lib/gitlab/markdown/pipeline.rb
+++ b/lib/gitlab/markdown/pipeline.rb
@@ -23,7 +23,7 @@ module Gitlab
define_method(meth) do |text, context|
context = transform_context(context)
- html_pipeline.send(meth, text, context)
+ html_pipeline.__send__(meth, text, context) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb
index 5d2d7d0026c..63c3372da51 100644
--- a/lib/gitlab/middleware/rails_queue_duration.rb
+++ b/lib/gitlab/middleware/rails_queue_duration.rb
@@ -8,7 +8,7 @@ module Gitlab
def initialize(app)
@app = app
end
-
+
def call(env)
trans = Gitlab::Metrics.current_transaction
proxy_start = env['HTTP_GITLAB_WORKHORSE_PROXY_START'].presence
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index cf461adf697..732fbf68dad 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -25,7 +25,9 @@ module Gitlab
end
TEMPLATES_TABLE = [
- ProjectTemplate.new('rails', 'Ruby on Rails')
+ ProjectTemplate.new('rails', 'Ruby on Rails'),
+ ProjectTemplate.new('spring', 'Spring'),
+ ProjectTemplate.new('express', 'NodeJS Express')
].freeze
class << self
diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb
index b0da516ff83..9bf019b72e6 100644
--- a/lib/gitlab/redis/cache.rb
+++ b/lib/gitlab/redis/cache.rb
@@ -7,9 +7,6 @@ module Gitlab
CACHE_NAMESPACE = 'cache:gitlab'.freeze
DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'.freeze
REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'.freeze
- if defined?(::Rails) && ::Rails.root.present?
- DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.cache.yml').freeze
- end
class << self
def default_url
@@ -22,7 +19,7 @@ module Gitlab
return file_name unless file_name.nil?
# otherwise, if config files exists for this class, use it
- file_name = File.expand_path(DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME, __dir__)
+ file_name = config_file_path('redis.cache.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb
index f9249d05565..e1695aafbeb 100644
--- a/lib/gitlab/redis/queues.rb
+++ b/lib/gitlab/redis/queues.rb
@@ -8,9 +8,6 @@ module Gitlab
MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'.freeze
REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'.freeze
- if defined?(::Rails) && ::Rails.root.present?
- DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.queues.yml').freeze
- end
class << self
def default_url
@@ -23,7 +20,7 @@ module Gitlab
return file_name if file_name
# otherwise, if config files exists for this class, use it
- file_name = File.expand_path(DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME, __dir__)
+ file_name = config_file_path('redis.queues.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 395dcf082da..10bec7a90da 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -7,9 +7,6 @@ module Gitlab
SESSION_NAMESPACE = 'session:gitlab'.freeze
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
- if defined?(::Rails) && ::Rails.root.present?
- DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.shared_state.yml').freeze
- end
class << self
def default_url
@@ -22,7 +19,7 @@ module Gitlab
return file_name if file_name
# otherwise, if config files exists for this class, use it
- file_name = File.expand_path(DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME, __dir__)
+ file_name = config_file_path('redis.shared_state.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_SHARED_STATE_URL when config file is absent
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
index c43b37dde74..8ad06480575 100644
--- a/lib/gitlab/redis/wrapper.rb
+++ b/lib/gitlab/redis/wrapper.rb
@@ -8,9 +8,6 @@ module Gitlab
class Wrapper
DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'.freeze
- if defined?(::Rails) && ::Rails.root.present?
- DEFAULT_REDIS_CONFIG_FILE_NAME = ::Rails.root.join('config', 'resque.yml').freeze
- end
class << self
delegate :params, :url, to: :new
@@ -49,13 +46,21 @@ module Gitlab
DEFAULT_REDIS_URL
end
+ # Return the absolute path to a Rails configuration file
+ #
+ # We use this instead of `Rails.root` because for certain tasks
+ # utilizing these classes, `Rails` might not be available.
+ def config_file_path(filename)
+ File.expand_path("../../../config/#{filename}", __dir__)
+ end
+
def config_file_name
# if ENV set for wrapper class, use it even if it points to a file does not exist
file_name = ENV[REDIS_CONFIG_ENV_VAR_NAME]
return file_name unless file_name.nil?
# otherwise, if config files exists for wrapper class, use it
- file_name = File.expand_path(DEFAULT_REDIS_CONFIG_FILE_NAME, __dir__)
+ file_name = config_file_path('resque.yml')
return file_name if File.file?(file_name)
# nil will force use of DEFAULT_REDIS_URL when config file is absent
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 0cb28732402..280a9abf03e 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -73,8 +73,10 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def add_repository(storage, name)
- gitlab_shell_fast_execute([gitlab_shell_projects_path,
- 'add-project', storage, "#{name}.git"])
+ Gitlab::Git::Repository.create(storage, name, bare: true, symlink_hooks_to: gitlab_shell_hooks_path)
+ rescue => err
+ Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
+ false
end
# Import repository
@@ -273,7 +275,11 @@ module Gitlab
protected
def gitlab_shell_path
- Gitlab.config.gitlab_shell.path
+ File.expand_path(Gitlab.config.gitlab_shell.path)
+ end
+
+ def gitlab_shell_hooks_path
+ File.expand_path(Gitlab.config.gitlab_shell.hooks_path)
end
def gitlab_shell_user_home
diff --git a/lib/gitlab/slash_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb
index ea611a4d629..ab855319077 100644
--- a/lib/gitlab/slash_commands/presenters/help.rb
+++ b/lib/gitlab/slash_commands/presenters/help.rb
@@ -14,7 +14,7 @@ module Gitlab
if text.start_with?('help')
header_with_list("Available commands", full_commands(trigger))
else
- header_with_list("Unknown command, these commands are available", full_commands(trigger))
+ header_with_list("Unknown command, these commands are available", full_commands(trigger))
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index fa182c4deda..9670c93759e 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -14,6 +14,19 @@ module Gitlab
str.force_encoding(Encoding::UTF_8)
end
+ # A slugified version of the string, suitable for inclusion in URLs and
+ # domain names. Rules:
+ #
+ # * Lowercased
+ # * Anything not matching [a-z0-9-] is replaced with a -
+ # * Maximum length is 63 bytes
+ # * First/Last Character is not a hyphen
+ def slugify(str)
+ return str.downcase
+ .gsub(/[^a-z0-9]/, '-')[0..62]
+ .gsub(/(\A-+|-+\z)/, '')
+ end
+
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3f25e463412..a362a3a0bc6 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -45,7 +45,6 @@ module Gitlab
raise "Unsupported action: #{action}"
end
if feature_enabled
- params[:GitalyAddress] = server[:address] # This field will be deprecated
params[:GitalyServer] = server
end
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index e337c67a0f5..08677a98fc1 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -18,7 +18,7 @@ namespace :gitlab do
command = status.zero? ? ['gmake'] : ['make']
if Rails.env.test?
- command += %W[BUNDLE_PATH=#{Bundler.bundle_path}]
+ command += %W[BUNDLE_PATH=#{Bundler.bundle_path}]
end
Dir.chdir(args.dir) do
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index a7e30423c7a..f44abc2b81b 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -21,13 +21,18 @@ namespace :gitlab do
params = {
import_url: template.clone_url,
namespace_id: admin.namespace.id,
- path: template.title,
+ path: template.name,
skip_wiki: true
}
- puts "Creating project for #{template.name}"
+ puts "Creating project for #{template.title}"
project = Projects::CreateService.new(admin, params).execute
+ unless project.persisted?
+ puts project.errors.messages
+ exit(1)
+ end
+
loop do
if project.finished?
puts "Import finished for #{template.name}"
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index 41dee5fdc06..4a3c40f88eb 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -27,7 +27,7 @@ class UploadedFile
alias_method :local_path, :path
def method_missing(method_name, *args, &block) #:nodoc:
- @tempfile.__send__(method_name, *args, &block)
+ @tempfile.__send__(method_name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
def respond_to?(method_name, include_private = false) #:nodoc:
diff --git a/package.json b/package.json
index c5247a63e67..cbb9be3a27f 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"vue-loader": "^11.3.4",
"vue-resource": "^1.3.4",
"vue-template-compiler": "^2.2.6",
- "webpack": "^3.4.0",
+ "webpack": "^3.5.4",
"webpack-bundle-analyzer": "^2.8.2",
"webpack-stats-plugin": "^0.1.5"
},
diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb
index 4f83a773645..12e56404cf6 100644
--- a/qa/qa/runtime/release.rb
+++ b/qa/qa/runtime/release.rb
@@ -21,7 +21,7 @@ module QA
end
def self.method_missing(name, *args)
- self.new.strategy.public_send(name, *args)
+ self.new.strategy.public_send(name, *args) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 5db42175c15..dbb0ae9c86e 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -74,7 +74,7 @@ feature 'Admin updates settings' do
context 'sign-in restrictions', :js do
it 'de-activates oauth sign-in source' do
find('.btn', text: 'GitLab.com').click
-
+
expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
end
end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 32b3e13c624..56144d17d4f 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -35,12 +35,12 @@ feature 'Group milestones', :js do
context 'milestones list' do
let!(:other_project) { create(:project_empty_repo, group: group) }
- let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') }
let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
- let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
+ let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
before do
visit group_milestones_path(group)
@@ -58,5 +58,30 @@ feature 'Group milestones', :js do
expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
end
+
+ it 'updates milestone' do
+ page.within(".milestones #milestone_#{active_group_milestone.id}") do
+ click_link('Edit')
+ end
+
+ page.within('.milestone-form') do
+ fill_in 'milestone_title', with: 'new title'
+ click_button('Update milestone')
+ end
+
+ expect(find('#content-body h2')).to have_content('new title')
+ end
+
+ it 'shows milestone detail and supports its edit' do
+ page.within(".milestones #milestone_#{active_group_milestone.id}") do
+ click_link(active_group_milestone.title)
+ end
+
+ page.within('.detail-page-header') do
+ click_link('Edit')
+ end
+
+ expect(page).to have_selector('.milestone-form')
+ end
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index a69bd8a09b7..2cc027aac9e 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -134,8 +134,10 @@ describe 'Dropdown assignee', :js do
it 'fills in the assignee username when the assignee has not been filtered' do
click_assignee(user_jacob.name)
+ wait_for_requests
+
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect_tokens([{ name: 'assignee', value: "@#{user_jacob.username}" }])
+ expect_tokens([assignee_token(user_jacob.name)])
expect_filtered_search_input_empty
end
@@ -143,8 +145,10 @@ describe 'Dropdown assignee', :js do
filtered_search.send_keys('roo')
click_assignee(user.name)
+ wait_for_requests
+
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
@@ -152,7 +156,7 @@ describe 'Dropdown assignee', :js do
find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect_tokens([{ name: 'assignee', value: 'none' }])
+ expect_tokens([assignee_token('none')])
expect_filtered_search_input_empty
end
end
@@ -171,7 +175,7 @@ describe 'Dropdown assignee', :js do
find('#js-dropdown-assignee .filter-dropdown-item', text: user.username).click
expect(page).to have_css(js_dropdown_assignee, visible: false)
- expect_tokens([{ name: 'assignee', value: user.username }])
+ expect_tokens([assignee_token(user.username)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 4bbf18e1dbe..975dc035f2d 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -121,16 +121,20 @@ describe 'Dropdown author', js: true do
it 'fills in the author username when the author has not been filtered' do
click_author(user_jacob.name)
+ wait_for_requests
+
expect(page).to have_css(js_dropdown_author, visible: false)
- expect_tokens([{ name: 'author', value: "@#{user_jacob.username}" }])
+ expect_tokens([author_token(user_jacob.name)])
expect_filtered_search_input_empty
end
it 'fills in the author username when the author has been filtered' do
click_author(user.name)
+ wait_for_requests
+
expect(page).to have_css(js_dropdown_author, visible: false)
- expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
end
@@ -149,7 +153,7 @@ describe 'Dropdown author', js: true do
find('#js-dropdown-author .filter-dropdown-item', text: user.username).click
expect(page).to have_css(js_dropdown_author, visible: false)
- expect_tokens([{ name: 'author', value: user.username }])
+ expect_tokens([author_token(user.username)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index 67eb0ef0119..e84b07ec2ef 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -47,7 +47,7 @@ describe 'Dropdown label', js: true do
filtered_search.native.send_keys(:down, :down, :enter)
- expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_tokens([label_token(bug_label.title)])
expect_filtered_search_input_empty
end
end
@@ -178,7 +178,7 @@ describe 'Dropdown label', js: true do
click_label(bug_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_tokens([label_token(bug_label.title)])
expect_filtered_search_input_empty
end
@@ -187,7 +187,7 @@ describe 'Dropdown label', js: true do
click_label(bug_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "~#{bug_label.title}" }])
+ expect_tokens([label_token(bug_label.title)])
expect_filtered_search_input_empty
end
@@ -195,7 +195,7 @@ describe 'Dropdown label', js: true do
click_label(two_words_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "\"#{two_words_label.title}\"" }])
+ expect_tokens([label_token("\"#{two_words_label.title}\"")])
expect_filtered_search_input_empty
end
@@ -203,7 +203,7 @@ describe 'Dropdown label', js: true do
click_label(long_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "\"#{long_label.title}\"" }])
+ expect_tokens([label_token("\"#{long_label.title}\"")])
expect_filtered_search_input_empty
end
@@ -211,7 +211,7 @@ describe 'Dropdown label', js: true do
click_label(wont_fix_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "~'#{wont_fix_label.title}'" }])
+ expect_tokens([label_token("'#{wont_fix_label.title}'")])
expect_filtered_search_input_empty
end
@@ -219,7 +219,7 @@ describe 'Dropdown label', js: true do
click_label(uppercase_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "~#{uppercase_label.title}" }])
+ expect_tokens([label_token(uppercase_label.title)])
expect_filtered_search_input_empty
end
@@ -227,7 +227,7 @@ describe 'Dropdown label', js: true do
click_label(special_label.title)
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: "~#{special_label.title}" }])
+ expect_tokens([label_token(special_label.title)])
expect_filtered_search_input_empty
end
@@ -235,7 +235,7 @@ describe 'Dropdown label', js: true do
find("#{js_dropdown_label} .filter-dropdown-item", text: 'No Label').click
expect(page).not_to have_css(js_dropdown_label)
- expect_tokens([{ name: 'label', value: 'none' }])
+ expect_tokens([label_token('none', false)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 456eb05f241..5f99921ae2e 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -134,7 +134,7 @@ describe 'Dropdown milestone', :js do
click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%#{milestone.title}" }])
+ expect_tokens([milestone_token(milestone.title)])
expect_filtered_search_input_empty
end
@@ -143,7 +143,7 @@ describe 'Dropdown milestone', :js do
click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%#{milestone.title}" }])
+ expect_tokens([milestone_token(milestone.title)])
expect_filtered_search_input_empty
end
@@ -151,7 +151,7 @@ describe 'Dropdown milestone', :js do
click_milestone(two_words_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%\"#{two_words_milestone.title}\"" }])
+ expect_tokens([milestone_token("\"#{two_words_milestone.title}\"")])
expect_filtered_search_input_empty
end
@@ -159,7 +159,7 @@ describe 'Dropdown milestone', :js do
click_milestone(long_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%\"#{long_milestone.title}\"" }])
+ expect_tokens([milestone_token("\"#{long_milestone.title}\"")])
expect_filtered_search_input_empty
end
@@ -167,7 +167,7 @@ describe 'Dropdown milestone', :js do
click_milestone(wont_fix_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%'#{wont_fix_milestone.title}'" }])
+ expect_tokens([milestone_token("'#{wont_fix_milestone.title}'")])
expect_filtered_search_input_empty
end
@@ -175,7 +175,7 @@ describe 'Dropdown milestone', :js do
click_milestone(uppercase_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%#{uppercase_milestone.title}" }])
+ expect_tokens([milestone_token(uppercase_milestone.title)])
expect_filtered_search_input_empty
end
@@ -183,7 +183,7 @@ describe 'Dropdown milestone', :js do
click_milestone(special_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: "%#{special_milestone.title}" }])
+ expect_tokens([milestone_token(special_milestone.title)])
expect_filtered_search_input_empty
end
@@ -191,7 +191,7 @@ describe 'Dropdown milestone', :js do
click_static_milestone('No Milestone')
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: 'none' }])
+ expect_tokens([milestone_token('none', false)])
expect_filtered_search_input_empty
end
@@ -199,7 +199,7 @@ describe 'Dropdown milestone', :js do
click_static_milestone('Upcoming')
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: 'upcoming' }])
+ expect_tokens([milestone_token('upcoming', false)])
expect_filtered_search_input_empty
end
@@ -207,7 +207,7 @@ describe 'Dropdown milestone', :js do
click_static_milestone('Started')
expect(page).to have_css(js_dropdown_milestone, visible: false)
- expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_tokens([milestone_token('started', false)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index cd2cbf4bfe7..2070043d842 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -97,7 +97,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched author' do
input_filtered_search("author:@#{user.username}")
- expect_tokens([{ name: 'author', value: user.username }])
+ wait_for_requests
+
+ expect_tokens([author_token(user.name)])
expect_issues_list_count(5)
expect_filtered_search_input_empty
end
@@ -117,7 +119,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched author and text' do
input_filtered_search("author:@#{user.username} #{search_term}")
- expect_tokens([{ name: 'author', value: user.username }])
+ wait_for_requests
+
+ expect_tokens([author_token(user.name)])
expect_issues_list_count(3)
expect_filtered_search_input(search_term)
end
@@ -125,10 +129,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched author, assignee and text' do
input_filtered_search("author:@#{user.username} assignee:@#{user.username} #{search_term}")
- expect_tokens([
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username }
- ])
+ wait_for_requests
+
+ expect_tokens([author_token(user.name), assignee_token(user.name)])
expect_issues_list_count(3)
expect_filtered_search_input(search_term)
end
@@ -136,10 +139,12 @@ describe 'Filter issues', js: true do
it 'filters issues by searched author, assignee, label, and text' do
input_filtered_search("author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username },
- { name: 'label', value: caps_sensitive_label.title }
+ author_token(user.name),
+ assignee_token(user.name),
+ label_token(caps_sensitive_label.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -148,11 +153,13 @@ describe 'Filter issues', js: true do
it 'filters issues by searched author, assignee, label, milestone and text' do
input_filtered_search("author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username },
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'milestone', value: milestone.title }
+ author_token(user.name),
+ assignee_token(user.name),
+ label_token(caps_sensitive_label.title),
+ milestone_token(milestone.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -169,7 +176,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched assignee' do
input_filtered_search("assignee:@#{user.username}")
- expect_tokens([{ name: 'assignee', value: user.username }])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name)])
expect_issues_list_count(5)
expect_filtered_search_input_empty
end
@@ -177,7 +186,7 @@ describe 'Filter issues', js: true do
it 'filters issues by no assignee' do
input_filtered_search('assignee:none')
- expect_tokens([{ name: 'assignee', value: 'none' }])
+ expect_tokens([assignee_token('none')])
expect_issues_list_count(8, 1)
expect_filtered_search_input_empty
end
@@ -197,7 +206,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched assignee and text' do
input_filtered_search("assignee:@#{user.username} #{search_term}")
- expect_tokens([{ name: 'assignee', value: user.username }])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name)])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
end
@@ -205,10 +216,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched assignee, author and text' do
input_filtered_search("assignee:@#{user.username} author:@#{user.username} #{search_term}")
- expect_tokens([
- { name: 'assignee', value: user.username },
- { name: 'author', value: user.username }
- ])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name), author_token(user.name)])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
end
@@ -216,10 +226,12 @@ describe 'Filter issues', js: true do
it 'filters issues by searched assignee, author, label, text' do
input_filtered_search("assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'assignee', value: user.username },
- { name: 'author', value: user.username },
- { name: 'label', value: caps_sensitive_label.title }
+ assignee_token(user.name),
+ author_token(user.name),
+ label_token(caps_sensitive_label.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -229,10 +241,10 @@ describe 'Filter issues', js: true do
input_filtered_search("assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} #{search_term}")
expect_tokens([
- { name: 'assignee', value: user.username },
- { name: 'author', value: user.username },
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'milestone', value: milestone.title }
+ assignee_token(user.name),
+ author_token(user.name),
+ label_token(caps_sensitive_label.title),
+ milestone_token(milestone.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -253,7 +265,7 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label' do
input_filtered_search("label:~#{bug_label.title}")
- expect_tokens([{ name: 'label', value: bug_label.title }])
+ expect_tokens([label_token(bug_label.title)])
expect_issues_list_count(2)
expect_filtered_search_input_empty
end
@@ -261,7 +273,7 @@ describe 'Filter issues', js: true do
it 'filters issues by no label' do
input_filtered_search('label:none')
- expect_tokens([{ name: 'label', value: 'none' }])
+ expect_tokens([label_token('none', false)])
expect_issues_list_count(9, 1)
expect_filtered_search_input_empty
end
@@ -274,8 +286,8 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title}")
expect_tokens([
- { name: 'label', value: bug_label.title },
- { name: 'label', value: caps_sensitive_label.title }
+ label_token(bug_label.title),
+ label_token(caps_sensitive_label.title)
])
expect_issues_list_count(1)
expect_filtered_search_input_empty
@@ -287,7 +299,8 @@ describe 'Filter issues', js: true do
special_issue.labels << special_label
input_filtered_search("label:~#{special_label.title}")
- expect_tokens([{ name: 'label', value: special_label.title }])
+
+ expect_tokens([label_token(special_label.title)])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -297,7 +310,7 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~#{new_label.title}")
- expect_tokens([{ name: 'label', value: new_label.title }])
+ expect_tokens([label_token(new_label.title)])
expect_no_issues_list()
expect_filtered_search_input_empty
end
@@ -311,25 +324,27 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~'#{special_multiple_label.title}'")
- # filtered search defaults quotations to double quotes
- expect_tokens([{ name: 'label', value: "\"#{special_multiple_label.title}\"" }])
+ # Check for search results (which makes sure that the page has changed)
expect_issues_list_count(1)
+ # filtered search defaults quotations to double quotes
+ expect_tokens([label_token("\"#{special_multiple_label.title}\"")])
+
expect_filtered_search_input_empty
end
it 'single quotes' do
input_filtered_search("label:~'#{multiple_words_label.title}'")
- expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
expect_issues_list_count(1)
+ expect_tokens([label_token("\"#{multiple_words_label.title}\"")])
expect_filtered_search_input_empty
end
it 'double quotes' do
input_filtered_search("label:~\"#{multiple_words_label.title}\"")
- expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
+ expect_tokens([label_token("\"#{multiple_words_label.title}\"")])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -341,7 +356,7 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~'#{double_quotes_label.title}'")
- expect_tokens([{ name: 'label', value: "'#{double_quotes_label.title}'" }])
+ expect_tokens([label_token("'#{double_quotes_label.title}'")])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -353,7 +368,7 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~\"#{single_quotes_label.title}\"")
- expect_tokens([{ name: 'label', value: "\"#{single_quotes_label.title}\"" }])
+ expect_tokens([label_token("\"#{single_quotes_label.title}\"")])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -363,7 +378,7 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label and text' do
input_filtered_search("label:~#{caps_sensitive_label.title} #{search_term}")
- expect_tokens([{ name: 'label', value: caps_sensitive_label.title }])
+ expect_tokens([label_token(caps_sensitive_label.title)])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
end
@@ -371,10 +386,9 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label, author and text' do
input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} #{search_term}")
- expect_tokens([
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username }
- ])
+ wait_for_requests
+
+ expect_tokens([label_token(caps_sensitive_label.title), author_token(user.name)])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
end
@@ -382,10 +396,12 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label, author, assignee and text' do
input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username }
+ label_token(caps_sensitive_label.title),
+ author_token(user.name),
+ assignee_token(user.name)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -395,10 +411,10 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} #{search_term}")
expect_tokens([
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username },
- { name: 'milestone', value: milestone.title }
+ label_token(caps_sensitive_label.title),
+ author_token(user.name),
+ assignee_token(user.name),
+ milestone_token(milestone.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -410,8 +426,8 @@ describe 'Filter issues', js: true do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} #{search_term}")
expect_tokens([
- { name: 'label', value: bug_label.title },
- { name: 'label', value: caps_sensitive_label.title }
+ label_token(bug_label.title),
+ label_token(caps_sensitive_label.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -420,10 +436,12 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label, label2, author and text' do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'label', value: bug_label.title },
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username }
+ label_token(bug_label.title),
+ label_token(caps_sensitive_label.title),
+ author_token(user.name)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -432,11 +450,13 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label, label2, author, assignee and text' do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'label', value: bug_label.title },
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username }
+ label_token(bug_label.title),
+ label_token(caps_sensitive_label.title),
+ author_token(user.name),
+ assignee_token(user.name)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -445,12 +465,14 @@ describe 'Filter issues', js: true do
it 'filters issues by searched label, label2, author, assignee, milestone and text' do
input_filtered_search("label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'label', value: bug_label.title },
- { name: 'label', value: caps_sensitive_label.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username },
- { name: 'milestone', value: milestone.title }
+ label_token(bug_label.title),
+ label_token(caps_sensitive_label.title),
+ author_token(user.name),
+ assignee_token(user.name),
+ milestone_token(milestone.title)
])
expect_issues_list_count(1)
expect_filtered_search_input(search_term)
@@ -467,7 +489,7 @@ describe 'Filter issues', js: true do
end
it 'displays in search bar' do
- expect_tokens([{ name: 'label', value: "\"#{multiple_words_label.title}\"" }])
+ expect_tokens([label_token("\"#{multiple_words_label.title}\"")])
expect_filtered_search_input_empty
end
end
@@ -484,7 +506,7 @@ describe 'Filter issues', js: true do
it 'filters issues by searched milestone' do
input_filtered_search("milestone:%#{milestone.title}")
- expect_tokens([{ name: 'milestone', value: milestone.title }])
+ expect_tokens([milestone_token(milestone.title)])
expect_issues_list_count(5)
expect_filtered_search_input_empty
end
@@ -492,7 +514,7 @@ describe 'Filter issues', js: true do
it 'filters issues by no milestone' do
input_filtered_search("milestone:none")
- expect_tokens([{ name: 'milestone', value: 'none' }])
+ expect_tokens([milestone_token('none', false)])
expect_issues_list_count(7, 1)
expect_filtered_search_input_empty
end
@@ -500,7 +522,7 @@ describe 'Filter issues', js: true do
it 'filters issues by upcoming milestones' do
input_filtered_search("milestone:upcoming")
- expect_tokens([{ name: 'milestone', value: 'upcoming' }])
+ expect_tokens([milestone_token('upcoming', false)])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -508,7 +530,7 @@ describe 'Filter issues', js: true do
it 'filters issues by started milestones' do
input_filtered_search("milestone:started")
- expect_tokens([{ name: 'milestone', value: 'started' }])
+ expect_tokens([milestone_token('started', false)])
expect_issues_list_count(5)
expect_filtered_search_input_empty
end
@@ -527,7 +549,7 @@ describe 'Filter issues', js: true do
input_filtered_search("milestone:%#{special_milestone.title}")
- expect_tokens([{ name: 'milestone', value: special_milestone.title }])
+ expect_tokens([milestone_token(special_milestone.title)])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end
@@ -537,7 +559,7 @@ describe 'Filter issues', js: true do
input_filtered_search("milestone:%#{new_milestone.title}")
- expect_tokens([{ name: 'milestone', value: new_milestone.title }])
+ expect_tokens([milestone_token(new_milestone.title)])
expect_no_issues_list()
expect_filtered_search_input_empty
end
@@ -549,7 +571,7 @@ describe 'Filter issues', js: true do
it 'filters issues by searched milestone and text' do
input_filtered_search("milestone:%#{milestone.title} #{search_term}")
- expect_tokens([{ name: 'milestone', value: milestone.title }])
+ expect_tokens([milestone_token(milestone.title)])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
end
@@ -557,9 +579,11 @@ describe 'Filter issues', js: true do
it 'filters issues by searched milestone, author and text' do
input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'milestone', value: milestone.title },
- { name: 'author', value: user.username }
+ milestone_token(milestone.title),
+ author_token(user.name)
])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
@@ -568,10 +592,12 @@ describe 'Filter issues', js: true do
it 'filters issues by searched milestone, author, assignee and text' do
input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'milestone', value: milestone.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username }
+ milestone_token(milestone.title),
+ author_token(user.name),
+ assignee_token(user.name)
])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
@@ -580,11 +606,13 @@ describe 'Filter issues', js: true do
it 'filters issues by searched milestone, author, assignee, label and text' do
input_filtered_search("milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} #{search_term}")
+ wait_for_requests
+
expect_tokens([
- { name: 'milestone', value: milestone.title },
- { name: 'author', value: user.username },
- { name: 'assignee', value: user.username },
- { name: 'label', value: bug_label.title }
+ milestone_token(milestone.title),
+ author_token(user.name),
+ assignee_token(user.name),
+ label_token(bug_label.title)
])
expect_issues_list_count(2)
expect_filtered_search_input(search_term)
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index aa9d0d842de..a432d031337 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -32,7 +32,7 @@ describe 'Search bar', js: true do
it 'selects item' do
filtered_search.native.send_keys(:down, :down, :enter)
- expect_tokens([{ name: 'author' }])
+ expect_tokens([author_token])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 52efe944b69..14a555fde10 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -346,8 +346,8 @@ describe 'Visual tokens', js: true do
it 'tokenizes the search term to complete visual token' do
expect_tokens([
- { name: 'author', value: '@root' },
- { name: 'assignee', value: 'none' }
+ author_token(user.name),
+ assignee_token('none')
])
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 3c8e37ff920..3ffc80622f5 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -708,7 +708,7 @@ describe 'Issues' do
end
describe 'confidential issue#show', js: true do
- it 'shows confidential sibebar information as confidential and can be turned off' do
+ it 'shows confidential sibebar information as confidential and can be turned off' do
issue = create(:issue, :confidential, project: project)
visit project_issue_path(project, issue)
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 2c560632a1b..2d2c674f8fb 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -28,11 +28,12 @@ feature 'Merge request conflict resolution', js: true do
end
click_button 'Commit conflict resolution'
- wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
+ wait_for_requests
+
click_on 'Changes'
wait_for_requests
@@ -69,10 +70,12 @@ feature 'Merge request conflict resolution', js: true do
end
click_button 'Commit conflict resolution'
- wait_for_requests
+
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
+ wait_for_requests
+
click_on 'Changes'
wait_for_requests
@@ -140,12 +143,13 @@ feature 'Merge request conflict resolution', js: true do
end
click_button 'Commit conflict resolution'
- wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
+ wait_for_requests
+
click_on 'Changes'
wait_for_requests
click_link 'Expand all'
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 521fcabc881..166c02a7a7f 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -25,7 +25,7 @@ feature 'Merge Request filtering by Milestone' do
visit_merge_requests(project)
input_filtered_search('milestone:none')
- expect_tokens([{ name: 'milestone', value: 'none' }])
+ expect_tokens([milestone_token('none', false)])
expect_filtered_search_input_empty
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 3686131fee4..b51ae0890e4 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -24,7 +24,9 @@ describe 'Filter merge requests' do
let(:search_query) { "assignee:@#{user.username}" }
def expect_assignee_visual_tokens
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
@@ -57,7 +59,7 @@ describe 'Filter merge requests' do
let(:search_query) { "milestone:%\"#{milestone.title}\"" }
def expect_milestone_visual_tokens
- expect_tokens([{ name: 'milestone', value: "%\"#{milestone.title}\"" }])
+ expect_tokens([milestone_token("\"#{milestone.title}\"")])
expect_filtered_search_input_empty
end
@@ -91,7 +93,7 @@ describe 'Filter merge requests' do
input_filtered_search('label:none')
expect_mr_list_count(1)
- expect_tokens([{ name: 'label', value: 'none' }])
+ expect_tokens([label_token('none', false)])
expect_filtered_search_input_empty
end
@@ -99,7 +101,7 @@ describe 'Filter merge requests' do
input_filtered_search("label:~#{label.title}")
expect_mr_list_count(0)
- expect_tokens([{ name: 'label', value: "~#{label.title}" }])
+ expect_tokens([label_token(label.title)])
expect_filtered_search_input_empty
end
@@ -107,10 +109,7 @@ describe 'Filter merge requests' do
input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
expect_mr_list_count(0)
- expect_tokens([
- { name: 'label', value: "~\"#{wontfix.title}\"" },
- { name: 'label', value: "~#{label.title}" }
- ])
+ expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
expect_filtered_search_input_empty
end
@@ -118,16 +117,13 @@ describe 'Filter merge requests' do
input_filtered_search("label:~\"#{wontfix.title}\"")
expect_mr_list_count(0)
- expect_tokens([{ name: 'label', value: "~\"#{wontfix.title}\"" }])
+ expect_tokens([label_token("\"#{wontfix.title}\"")])
expect_filtered_search_input_empty
input_filtered_search_keys("label:~#{label.title}")
expect_mr_list_count(0)
- expect_tokens([
- { name: 'label', value: "~\"#{wontfix.title}\"" },
- { name: 'label', value: "~#{label.title}" }
- ])
+ expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
expect_filtered_search_input_empty
end
end
@@ -143,10 +139,9 @@ describe 'Filter merge requests' do
context 'assignee and label', js: true do
def expect_assignee_label_visual_tokens
- expect_tokens([
- { name: 'assignee', value: "@#{user.username}" },
- { name: 'label', value: "~#{label.title}" }
- ])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name), label_token(label.title)])
expect_filtered_search_input_empty
end
@@ -214,7 +209,7 @@ describe 'Filter merge requests' do
input_filtered_search_keys(' label:~bug')
expect_mr_list_count(1)
- expect_tokens([{ name: 'label', value: '~bug' }])
+ expect_tokens([label_token('bug')])
expect_filtered_search_input('Bug')
end
@@ -227,7 +222,7 @@ describe 'Filter merge requests' do
input_filtered_search_keys(' milestone:%8')
expect_mr_list_count(1)
- expect_tokens([{ name: 'milestone', value: '%8' }])
+ expect_tokens([milestone_token('8')])
expect_filtered_search_input('Bug')
end
@@ -240,7 +235,10 @@ describe 'Filter merge requests' do
input_filtered_search_keys(" assignee:@#{user.username}")
expect_mr_list_count(1)
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input('Bug')
end
@@ -252,8 +250,10 @@ describe 'Filter merge requests' do
input_filtered_search_keys(" author:@#{user.username}")
+ wait_for_requests
+
expect_mr_list_count(1)
- expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_tokens([author_token(user.name)])
expect_filtered_search_input('Bug')
end
end
@@ -293,7 +293,9 @@ describe 'Filter merge requests' do
it 'filter by current user' do
visit project_merge_requests_path(project, assignee_id: user.id)
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ wait_for_requests
+
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
@@ -303,7 +305,9 @@ describe 'Filter merge requests' do
visit project_merge_requests_path(project, assignee_id: new_user.id)
- expect_tokens([{ name: 'assignee', value: "@#{new_user.username}" }])
+ wait_for_requests
+
+ expect_tokens([assignee_token(new_user.name)])
expect_filtered_search_input_empty
end
end
@@ -312,7 +316,9 @@ describe 'Filter merge requests' do
it 'filter by current user' do
visit project_merge_requests_path(project, author_id: user.id)
- expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ wait_for_requests
+
+ expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
@@ -322,7 +328,9 @@ describe 'Filter merge requests' do
visit project_merge_requests_path(project, author_id: new_user.id)
- expect_tokens([{ name: 'author', value: "@#{new_user.username}" }])
+ wait_for_requests
+
+ expect_tokens([author_token(new_user.name)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index 20303359c46..624f13922ed 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -8,7 +8,7 @@ describe 'Milestone show' do
let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } }
before do
- project.add_user(user, :developer)
+ project.add_user(user, :developer)
sign_in(user)
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 4238d25e9ee..9bcd5beabb8 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -20,7 +20,7 @@ feature 'Template Undo Button', js: true do
end
end
- context 'creating a non-matching file' do
+ context 'creating a non-matching file' do
before do
visit project_new_blob_path(project, 'master')
select_file_template_type('LICENSE')
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index c9ba1a8c088..8abd4403065 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projected Tags', js: true do
+feature 'Protected Tags', js: true do
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 9b49fc2225d..6742d77937f 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -195,37 +195,33 @@ describe "Search" do
it 'takes user to her issues page when issues assigned is clicked' do
find('.dropdown-menu').click_link 'Issues assigned to me'
- sleep 2
expect(page).to have_selector('.filtered-search')
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'takes user to her issues page when issues authored is clicked' do
find('.dropdown-menu').click_link "Issues I've created"
- sleep 2
expect(page).to have_selector('.filtered-search')
- expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
it 'takes user to her MR page when MR assigned is clicked' do
find('.dropdown-menu').click_link 'Merge requests assigned to me'
- sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
+ expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'takes user to her MR page when MR authored is clicked' do
find('.dropdown-menu').click_link "Merge requests I've created"
- sleep 2
expect(page).to have_selector('.merge-requests-holder')
- expect_tokens([{ name: 'author', value: "@#{user.username}" }])
+ expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 37a5e6b474e..d1efa318d14 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -432,9 +432,7 @@ describe ProjectsHelper do
end
describe '#any_projects?' do
- before do
- create(:project)
- end
+ let!(:project) { create(:project) }
it 'returns true when projects will be returned' do
expect(helper.any_projects?(Project.all)).to eq(true)
@@ -444,6 +442,14 @@ describe ProjectsHelper do
expect(helper.any_projects?(Project.none)).to eq(false)
end
+ it 'returns true when using a non-empty Array' do
+ expect(helper.any_projects?([project])).to eq(true)
+ end
+
+ it 'returns false when using an empty Array' do
+ expect(helper.any_projects?([])).to eq(false)
+ end
+
it 'only executes a single query when a LIMIT is applied' do
relation = Project.limit(1)
recorder = ActiveRecord::QueryRecorder.new do
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index 889fe441171..5eba03ef576 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -23,7 +23,7 @@ describe VersionCheckHelper do
end
it 'should have a js prefixed css class' do
- expect(@image_tag).to match(/class="js-version-status-badge"/)
+ expect(@image_tag).to match(/class="js-version-status-badge lazy"/)
end
it 'should have a VersionCheck url as the src' do
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index 7a46e47bb15..7968c9425f2 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -7,7 +7,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:prometheus_service, project: project) }
-
+
render_views
before(:all) do
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 0a3c64d5d31..80915c32a74 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -7,7 +7,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
-
render_views
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index 65a7459c5ed..2e81a1b056b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -10,6 +10,7 @@ import {
mousePos,
getHideSubItemsInterval,
documentMouseMove,
+ getHeaderHeight,
} from '~/fly_out_nav';
import bp from '~/breakpoints';
@@ -59,7 +60,7 @@ describe('Fly out sidebar navigation', () => {
describe('getHideSubItemsInterval', () => {
beforeEach(() => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 50px;"></div>';
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
});
it('returns 0 if currentOpenMenu is nil', () => {
@@ -112,6 +113,7 @@ describe('Fly out sidebar navigation', () => {
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
+ console.log(el);
expect(
getHideSubItemsInterval(),
@@ -245,7 +247,7 @@ describe('Fly out sidebar navigation', () => {
expect(
subItems.style.transform,
- ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top)}px, 0px)`);
+ ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
});
it('sets is-above when element is above', () => {
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index db2b7d51626..249a2f36fcd 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -1,57 +1,57 @@
import Vue from 'vue';
import repoCommitSection from '~/repo/components/repo_commit_section.vue';
import RepoStore from '~/repo/stores/repo_store';
-import RepoHelper from '~/repo/helpers/repo_helper';
import Api from '~/api';
describe('RepoCommitSection', () => {
const branch = 'master';
const projectUrl = 'projectUrl';
- const openedFiles = [{
+ const changedFiles = [{
id: 0,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`,
+ path: 'dir/file0.ext',
newContent: 'a',
}, {
id: 1,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`,
+ path: 'dir/file1.ext',
newContent: 'b',
- }, {
+ }];
+ const openedFiles = changedFiles.concat([{
id: 2,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`,
+ path: 'dir/file2.ext',
changed: false,
- }];
+ }]);
RepoStore.projectUrl = projectUrl;
- function createComponent() {
+ function createComponent(el) {
const RepoCommitSection = Vue.extend(repoCommitSection);
- return new RepoCommitSection().$mount();
+ return new RepoCommitSection().$mount(el);
}
it('renders a commit section', () => {
RepoStore.isCommitable = true;
+ RepoStore.currentBranch = branch;
RepoStore.targetBranch = branch;
RepoStore.openedFiles = openedFiles;
- spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
-
const vm = createComponent();
- const changedFiles = [...vm.$el.querySelectorAll('.changed-files > li')];
+ const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')];
const commitMessage = vm.$el.querySelector('#commit-message');
- const submitCommit = vm.$el.querySelector('.submit-commit');
+ const submitCommit = vm.$refs.submitCommit;
const targetBranch = vm.$el.querySelector('.target-branch');
expect(vm.$el.querySelector(':scope > form')).toBeTruthy();
- expect(vm.$el.querySelector('.staged-files').textContent).toEqual('Staged files (2)');
- expect(changedFiles.length).toEqual(2);
+ expect(vm.$el.querySelector('.staged-files').textContent.trim()).toEqual('Staged files (2)');
+ expect(changedFileElements.length).toEqual(2);
- changedFiles.forEach((changedFile, i) => {
- const filePath = RepoHelper.getFilePathFromFullPath(openedFiles[i].url, branch);
-
- expect(changedFile.textContent).toEqual(filePath);
+ changedFileElements.forEach((changedFile, i) => {
+ expect(changedFile.textContent.trim()).toEqual(changedFiles[i].path);
});
expect(commitMessage.tagName).toEqual('TEXTAREA');
@@ -59,9 +59,9 @@ describe('RepoCommitSection', () => {
expect(submitCommit.type).toEqual('submit');
expect(submitCommit.disabled).toBeTruthy();
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy();
- expect(vm.$el.querySelector('.commit-summary').textContent).toEqual('Commit 2 files');
- expect(targetBranch.querySelector(':scope > label').textContent).toEqual('Target branch');
- expect(targetBranch.querySelector('.help-block').textContent).toEqual(branch);
+ expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files');
+ expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch');
+ expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual(branch);
});
it('does not render if not isCommitable', () => {
@@ -89,14 +89,20 @@ describe('RepoCommitSection', () => {
const projectId = 'projectId';
const commitMessage = 'commitMessage';
RepoStore.isCommitable = true;
+ RepoStore.currentBranch = branch;
+ RepoStore.targetBranch = branch;
RepoStore.openedFiles = openedFiles;
RepoStore.projectId = projectId;
- spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
+ // We need to append to body to get form `submit` events working
+ // Otherwise we run into, "Form submission canceled because the form is not connected"
+ // See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ const el = document.createElement('div');
+ document.body.appendChild(el);
- const vm = createComponent();
+ const vm = createComponent(el);
const commitMessageEl = vm.$el.querySelector('#commit-message');
- const submitCommit = vm.$el.querySelector('.submit-commit');
+ const submitCommit = vm.$refs.submitCommit;
vm.commitMessage = commitMessage;
@@ -124,10 +130,8 @@ describe('RepoCommitSection', () => {
expect(actions[1].action).toEqual('update');
expect(actions[0].content).toEqual(openedFiles[0].newContent);
expect(actions[1].content).toEqual(openedFiles[1].newContent);
- expect(actions[0].file_path)
- .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[0].url, branch));
- expect(actions[1].file_path)
- .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[1].url, branch));
+ expect(actions[0].file_path).toEqual(openedFiles[0].path);
+ expect(actions[1].file_path).toEqual(openedFiles[1].path);
done();
});
@@ -140,7 +144,6 @@ describe('RepoCommitSection', () => {
const vm = {
submitCommitsLoading: true,
changedFiles: new Array(10),
- openedFiles: new Array(10),
commitMessage: 'commitMessage',
editMode: true,
};
@@ -149,7 +152,6 @@ describe('RepoCommitSection', () => {
expect(vm.submitCommitsLoading).toEqual(false);
expect(vm.changedFiles).toEqual([]);
- expect(vm.openedFiles).toEqual([]);
expect(vm.commitMessage).toEqual('');
expect(vm.editMode).toEqual(false);
});
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
index df2f9697acc..29dc2d21e4b 100644
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ b/spec/javascripts/repo/components/repo_edit_button_spec.js
@@ -12,18 +12,22 @@ describe('RepoEditButton', () => {
it('renders an edit button that toggles the view state', (done) => {
RepoStore.isCommitable = true;
RepoStore.changedFiles = [];
+ RepoStore.binary = false;
+ RepoStore.openedFiles = [{}, {}];
const vm = createComponent();
expect(vm.$el.tagName).toEqual('BUTTON');
expect(vm.$el.textContent).toMatch('Edit');
- spyOn(vm, 'editClicked').and.callThrough();
+ spyOn(vm, 'editCancelClicked').and.callThrough();
+ spyOn(vm, 'toggleProjectRefsForm');
vm.$el.click();
Vue.nextTick(() => {
- expect(vm.editClicked).toHaveBeenCalled();
+ expect(vm.editCancelClicked).toHaveBeenCalled();
+ expect(vm.toggleProjectRefsForm).toHaveBeenCalled();
expect(vm.$el.textContent).toMatch('Cancel edit');
done();
});
@@ -38,14 +42,10 @@ describe('RepoEditButton', () => {
});
describe('methods', () => {
- describe('editClicked', () => {
- it('sets dialog to open when there are changedFiles', () => {
+ describe('editCancelClicked', () => {
+ it('sets dialog to open when there are changedFiles');
- });
-
- it('toggles editMode and calls toggleBlobView', () => {
-
- });
+ it('toggles editMode and calls toggleBlobView');
});
});
});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 35e0c995163..85d55d171f9 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -1,26 +1,49 @@
import Vue from 'vue';
import repoEditor from '~/repo/components/repo_editor.vue';
-import RepoStore from '~/repo/stores/repo_store';
describe('RepoEditor', () => {
- function createComponent() {
+ beforeEach(() => {
const RepoEditor = Vue.extend(repoEditor);
- return new RepoEditor().$mount();
- }
+ this.vm = new RepoEditor().$mount();
+ });
+
+ it('renders an ide container', (done) => {
+ this.vm.openedFiles = ['idiidid'];
+ this.vm.binary = false;
- it('renders an ide container', () => {
- const monacoInstance = jasmine.createSpyObj('monacoInstance', ['onMouseUp', 'onKeyUp', 'setModel', 'updateOptions']);
- const monaco = {
- editor: jasmine.createSpyObj('editor', ['create']),
- };
- RepoStore.monaco = monaco;
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(false);
+ expect(this.vm.$el.id).toEqual('ide');
+ expect(this.vm.$el.tagName).toBe('DIV');
+ done();
+ });
+ });
- monaco.editor.create.and.returnValue(monacoInstance);
- spyOn(repoEditor.watch, 'blobRaw');
+ describe('when there are no open files', () => {
+ it('does not render the ide', (done) => {
+ this.vm.openedFiles = [];
+
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(true);
+ expect(this.vm.$el.tagName).not.toBeDefined();
+ done();
+ });
+ });
+ });
- const vm = createComponent();
+ describe('when open file is binary and not raw', () => {
+ it('does not render the IDE', (done) => {
+ this.vm.binary = true;
+ this.vm.activeFile = {
+ raw: false,
+ };
- expect(vm.$el.id).toEqual('ide');
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(true);
+ expect(this.vm.$el.tagName).not.toBeDefined();
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
index e1f25e4485f..dfab51710c3 100644
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js
@@ -23,6 +23,7 @@ describe('RepoFileButtons', () => {
RepoStore.activeFile = activeFile;
RepoStore.activeFileLabel = activeFileLabel;
RepoStore.editMode = true;
+ RepoStore.binary = false;
const vm = createComponent();
const raw = vm.$el.querySelector('.raw');
@@ -31,13 +32,13 @@ describe('RepoFileButtons', () => {
expect(vm.$el.id).toEqual('repo-file-buttons');
expect(raw.href).toMatch(`/${activeFile.raw_path}`);
- expect(raw.textContent).toEqual('Raw');
+ expect(raw.textContent.trim()).toEqual('Raw');
expect(blame.href).toMatch(`/${activeFile.blame_path}`);
- expect(blame.textContent).toEqual('Blame');
+ expect(blame.textContent.trim()).toEqual('Blame');
expect(history.href).toMatch(`/${activeFile.commits_path}`);
- expect(history.textContent).toEqual('History');
- expect(vm.$el.querySelector('.permalink').textContent).toEqual('Permalink');
- expect(vm.$el.querySelector('.preview').textContent).toEqual(activeFileLabel);
+ expect(history.textContent.trim()).toEqual('History');
+ expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
+ expect(vm.$el.querySelector('.preview').textContent.trim()).toEqual(activeFileLabel);
});
it('triggers rawPreviewToggle on preview click', () => {
@@ -71,12 +72,4 @@ describe('RepoFileButtons', () => {
expect(vm.$el.querySelector('.preview')).toBeFalsy();
});
-
- it('does not render if not isMini', () => {
- RepoStore.openedFiles = [];
-
- const vm = createComponent();
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index 90616ae13ca..518a2d25ecf 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -39,9 +39,9 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px');
expect(name.title).toEqual(file.url);
expect(name.href).toMatch(`/${file.url}`);
- expect(name.textContent).toEqual(file.name);
- expect(vm.$el.querySelector('.commit-message').textContent).toBe(file.lastCommitMessage);
- expect(vm.$el.querySelector('.commit-update').textContent).toBe(updated);
+ expect(name.textContent.trim()).toEqual(file.name);
+ expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(file.lastCommitMessage);
+ expect(vm.$el.querySelector('.commit-update').textContent.trim()).toBe(updated);
expect(fileIcon.classList.contains(file.icon)).toBeTruthy();
expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`);
});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
index d84f4c5609e..a030314d749 100644
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ b/spec/javascripts/repo/components/repo_loading_file_spec.js
@@ -13,7 +13,7 @@ describe('RepoLoadingFile', () => {
function assertLines(lines) {
lines.forEach((line, n) => {
const index = n + 1;
- expect(line.classList.contains(`line-of-code-${index}`)).toBeTruthy();
+ expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
});
}
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js
index edd27d3afb8..abcff8e537e 100644
--- a/spec/javascripts/repo/components/repo_sidebar_spec.js
+++ b/spec/javascripts/repo/components/repo_sidebar_spec.js
@@ -15,6 +15,7 @@ describe('RepoSidebar', () => {
RepoStore.files = [{
id: 0,
}];
+ RepoStore.openedFiles = [];
const vm = createComponent();
const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody');
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index a3b2d5dea82..d2a790ad73a 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -21,7 +21,7 @@ describe('RepoTab', () => {
const close = vm.$el.querySelector('.close');
const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
- spyOn(vm, 'xClicked');
+ spyOn(vm, 'closeTab');
spyOn(vm, 'tabClicked');
expect(close.querySelector('.fa-times')).toBeTruthy();
@@ -30,7 +30,7 @@ describe('RepoTab', () => {
close.click();
name.click();
- expect(vm.xClicked).toHaveBeenCalledWith(tab);
+ expect(vm.closeTab).toHaveBeenCalledWith(tab);
expect(vm.tabClicked).toHaveBeenCalledWith(tab);
});
@@ -48,22 +48,22 @@ describe('RepoTab', () => {
});
describe('methods', () => {
- describe('xClicked', () => {
+ describe('closeTab', () => {
const vm = jasmine.createSpyObj('vm', ['$emit']);
it('returns undefined and does not $emit if file is changed', () => {
const file = { changed: true };
- const returnVal = repoTab.methods.xClicked.call(vm, file);
+ const returnVal = repoTab.methods.closeTab.call(vm, file);
expect(returnVal).toBeUndefined();
expect(vm.$emit).not.toHaveBeenCalled();
});
- it('$emits xclicked event with file obj', () => {
+ it('$emits tabclosed event with file obj', () => {
const file = { changed: false };
- repoTab.methods.xClicked.call(vm, file);
+ repoTab.methods.closeTab.call(vm, file);
- expect(vm.$emit).toHaveBeenCalledWith('xclicked', file);
+ expect(vm.$emit).toHaveBeenCalledWith('tabclosed', file);
});
});
});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index 60459e90c48..a02b54efafc 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -29,22 +29,14 @@ describe('RepoTabs', () => {
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
});
- it('does not render a tabs list if not isMini', () => {
- RepoStore.openedFiles = [];
-
- const vm = createComponent();
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
-
describe('methods', () => {
- describe('xClicked', () => {
+ describe('tabClosed', () => {
it('calls removeFromOpenedFiles with file obj', () => {
const file = {};
spyOn(RepoStore, 'removeFromOpenedFiles');
- repoTabs.methods.xClicked(file);
+ repoTabs.methods.tabClosed(file);
expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file);
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 90eac1ed1ab..88a33caf2e3 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -41,7 +41,7 @@ describe('Confidential Issue Sidebar Block', () => {
).toBe(true);
expect(
- vm2.$el.innerHTML.includes('None'),
+ vm2.$el.innerHTML.includes('Not confidential'),
).toBe(true);
});
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index 49501931dd2..c44bc1840df 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -24,13 +24,13 @@ describe FileSizeValidator do
describe 'options uses a symbol' do
let(:options) do
{
- maximum: :test,
+ maximum: :max_attachment_size,
attributes: { attachment: attachment }
}
end
before do
- allow(note).to receive(:test) { 10 }
+ expect(note).to receive(:max_attachment_size) { 10 }
end
it 'attachment exceeds maximum limit' do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index ebe5af56160..e5555546fa8 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -295,7 +295,7 @@ describe Gitlab::Ci::Trace::Stream do
end
context 'malicious regexp' do
- let(:data) { malicious_text }
+ let(:data) { malicious_text }
let(:regex) { malicious_regexp }
include_examples 'malicious regexp'
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 854aaa34c73..0560c47f03f 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -6,10 +6,10 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:user) { create(:user, :admin) }
let(:start_time_attrs) { Issue.arel_table[:created_at] }
let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
- let(:options) do
+ let(:options) do
{ start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs,
- from: 30.days.ago }
+ from: 30.days.ago }
end
subject do
diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
index 9d1763b96ad..c86353abb7c 100644
--- a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
+++ b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
@@ -1,9 +1,30 @@
require 'spec_helper'
describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: true, broken_storage: true do
- let(:circuit_breaker) { described_class.new('default') }
+ let(:storage_name) { 'default' }
+ let(:circuit_breaker) { described_class.new(storage_name) }
let(:hostname) { Gitlab::Environment.hostname }
- let(:cache_key) { "storage_accessible:default:#{hostname}" }
+ let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
+
+ before do
+ # Override test-settings for the circuitbreaker with something more realistic
+ # for these specs.
+ stub_storage_settings('default' => {
+ 'path' => TestEnv.repos_path,
+ 'failure_count_threshold' => 10,
+ 'failure_wait_time' => 30,
+ 'failure_reset_time' => 1800,
+ 'storage_timeout' => 5
+ },
+ 'broken' => {
+ 'path' => 'tmp/tests/non-existent-repositories',
+ 'failure_count_threshold' => 10,
+ 'failure_wait_time' => 30,
+ 'failure_reset_time' => 1800,
+ 'storage_timeout' => 5
+ }
+ )
+ end
def value_from_redis(name)
Gitlab::Git::Storage.redis.with do |redis|
@@ -96,14 +117,14 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
describe '#circuit_broken?' do
- it 'is closed when there is no last failure' do
+ it 'is working when there is no last failure' do
set_in_redis(:last_failure, nil)
set_in_redis(:failure_count, 0)
expect(circuit_breaker.circuit_broken?).to be_falsey
end
- it 'is open when there was a recent failure' do
+ it 'is broken when there was a recent failure' do
Timecop.freeze do
set_in_redis(:last_failure, 1.second.ago.to_f)
set_in_redis(:failure_count, 1)
@@ -112,16 +133,34 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- it 'is open when there are to many failures' do
+ it 'is broken when there are too many failures' do
set_in_redis(:last_failure, 1.day.ago.to_f)
set_in_redis(:failure_count, 200)
expect(circuit_breaker.circuit_broken?).to be_truthy
end
+
+ context 'the `failure_wait_time` is set to 0' do
+ before do
+ stub_storage_settings('default' => {
+ 'failure_wait_time' => 0,
+ 'path' => TestEnv.repos_path
+ })
+ end
+
+ it 'is working even when there is a recent failure' do
+ Timecop.freeze do
+ set_in_redis(:last_failure, 0.seconds.ago.to_f)
+ set_in_redis(:failure_count, 1)
+
+ expect(circuit_breaker.circuit_broken?).to be_falsey
+ end
+ end
+ end
end
describe "storage_available?" do
- context 'when the storage is available' do
+ context 'the storage is available' do
it 'tracks that the storage was accessible an raises the error' do
expect(circuit_breaker).to receive(:track_storage_accessible)
@@ -136,8 +175,8 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- context 'when storage is not available' do
- let(:circuit_breaker) { described_class.new('broken') }
+ context 'storage is not available' do
+ let(:storage_name) { 'broken' }
it 'tracks that the storage was inaccessible' do
expect(circuit_breaker).to receive(:track_storage_inaccessible)
@@ -158,8 +197,8 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state:
end
end
- context 'when the storage is not available' do
- let(:circuit_breaker) { described_class.new('broken') }
+ context 'the storage is not available' do
+ let(:storage_name) { 'broken' }
it 'raises an error' do
expect(circuit_breaker).to receive(:track_storage_inaccessible)
diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb
index f5fd5a96bc9..d643dc5342d 100644
--- a/spec/lib/gitlab/key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/key_fingerprint_spec.rb
@@ -30,8 +30,8 @@ describe Gitlab::KeyFingerprint, lib: true do
MD5_FINGERPRINTS = {
rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd',
- ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
- ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
+ ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
+ ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b'
}.freeze
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 57a91193004..8370adf9211 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -4,8 +4,8 @@ describe Gitlab::LDAP::AuthHash do
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
- uid: '123456',
- provider: 'ldapmain',
+ uid: '123456',
+ provider: 'ldapmain',
info: info,
extra: {
raw_info: raw_info
@@ -33,11 +33,11 @@ describe Gitlab::LDAP::AuthHash do
context "without overridden attributes" do
it "has the correct username" do
- expect(auth_hash.username).to eq("123456")
+ expect(auth_hash.username).to eq("123456")
end
it "has the correct name" do
- expect(auth_hash.name).to eq("Smith, J.")
+ expect(auth_hash.name).to eq("Smith, J.")
end
end
@@ -54,11 +54,11 @@ describe Gitlab::LDAP::AuthHash do
end
it "has the correct username" do
- expect(auth_hash.username).to eq("johnsmith@example.com")
+ expect(auth_hash.username).to eq("johnsmith@example.com")
end
it "has the correct name" do
- expect(auth_hash.name).to eq("John Smith")
+ expect(auth_hash.name).to eq("John Smith")
end
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 15edb820908..2cf0f7516de 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -481,7 +481,7 @@ describe Gitlab::OAuth::User do
email: 'admin@othermail.com'
}
end
-
+
it 'generates the username with a counter' do
expect(gl_user.username).to eq('admin1')
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 12e75cdd5d0..d19bd611919 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -4,7 +4,9 @@ describe Gitlab::ProjectTemplate do
describe '.all' do
it 'returns a all templates' do
expected = [
- described_class.new('rails', 'Ruby on Rails')
+ described_class.new('rails', 'Ruby on Rails'),
+ described_class.new('spring', 'Spring'),
+ described_class.new('express', 'NodeJS Express')
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index d7df4e35c31..5589db92b1d 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
queries: [{ query_range: 'query_range_empty' }]
- group: group_b
priority: 1
- metrics:
+ metrics:
- title: title
required_metrics: ['metric_a']
weight: 1
@@ -148,7 +148,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
- group: group_a
priority: 1
metrics:
- - title:
+ - title:
required_metrics: []
weight: 1
queries: []
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index e1becd0a614..0c22a0d62cc 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -17,4 +17,11 @@ describe Gitlab::Redis::Wrapper do
let(:class_redis_url) { Gitlab::Redis::Wrapper::DEFAULT_REDIS_URL }
include_examples "redis_shared_examples"
+
+ describe '.config_file_path' do
+ it 'returns the absolute path to the configuration file' do
+ expect(described_class.config_file_path('foo.yml'))
+ .to eq Rails.root.join('config', 'foo.yml').to_s
+ end
+ end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 2345874cf10..cfadee0bcf5 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -94,28 +94,41 @@ describe Gitlab::Shell do
end
describe 'projects commands' do
- let(:projects_path) { 'tmp/tests/shell-projects-test/bin/gitlab-projects' }
+ let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
+ let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
+ let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }
before do
- allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-projects-test')
+ allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
+ allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
describe '#add_repository' do
- it 'returns true when the command succeeds' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
- nil, popen_vars).and_return([nil, 0])
+ it 'creates a repository' do
+ created_path = File.join(TestEnv.repos_path, 'project', 'path.git')
+ hooks_path = File.join(created_path, 'hooks')
+
+ begin
+ result = gitlab_shell.add_repository(TestEnv.repos_path, 'project/path')
+
+ repo_stat = File.stat(created_path) rescue nil
+ hooks_stat = File.lstat(hooks_path) rescue nil
+ hooks_dir = File.realpath(hooks_path)
+ ensure
+ FileUtils.rm_rf(created_path)
+ end
- expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be true
+ expect(result).to be_truthy
+ expect(repo_stat.mode & 0o777).to eq(0o770)
+ expect(hooks_stat.symlink?).to be_truthy
+ expect(hooks_dir).to eq(gitlab_shell_hooks_path)
end
it 'returns false when the command fails' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
- nil, popen_vars).and_return(["error", 1])
+ expect(FileUtils).to receive(:mkdir_p).and_raise(Errno::EEXIST)
- expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be false
+ expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be_falsy
end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 111c873f79c..92787bb262e 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,7 +1,21 @@
require 'spec_helper'
describe Gitlab::Utils do
- delegate :to_boolean, :boolean_to_yes_no, to: :described_class
+ delegate :to_boolean, :boolean_to_yes_no, :slugify, to: :described_class
+
+ describe '.slugify' do
+ {
+ 'TEST' => 'test',
+ 'project_with_underscores' => 'project-with-underscores',
+ 'namespace/project' => 'namespace-project',
+ 'a' * 70 => 'a' * 63,
+ 'test_trailing_' => 'test-trailing'
+ }.each do |original, expected|
+ it "slugifies #{original} to #{expected}" do
+ expect(slugify(original)).to eq(expected)
+ end
+ end
+ end
describe '.to_boolean' do
it 'accepts booleans' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index e78892d4232..b66afafa174 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -202,7 +202,6 @@ describe Gitlab::Workhorse do
context 'when Gitaly is enabled' do
let(:gitaly_params) do
{
- GitalyAddress: Gitlab::GitalyClient.address('default'),
GitalyServer: {
address: Gitlab::GitalyClient.address('default'),
token: Gitlab::GitalyClient.token('default')
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index cfe1ca481b2..81366d15b34 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -10,7 +10,7 @@ describe MigrateOldArtifacts do
before do
allow(Gitlab.config.artifacts).to receive(:path).and_return(directory)
end
-
+
after do
FileUtils.remove_entry_secure(directory)
end
@@ -95,7 +95,7 @@ describe MigrateOldArtifacts do
FileUtils.copy(
Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
File.join(legacy_path(build), "ci_build_artifacts.zip"))
-
+
FileUtils.copy(
Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
File.join(legacy_path(build), "ci_build_artifacts_metadata.gz"))
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
new file mode 100644
index 00000000000..e393374028f
--- /dev/null
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb')
+
+describe RemoveDuplicateMrEvents, truncate: true do
+ let(:migration) { described_class.new }
+
+ describe '#up' do
+ let(:user) { create(:user) }
+ let(:merge_requests) { create_list(:merge_request, 2) }
+ let(:issue) { create(:issue) }
+ let!(:events) do
+ [
+ create(:event, :created, author: user, target: merge_requests.first),
+ create(:event, :created, author: user, target: merge_requests.first),
+ create(:event, :updated, author: user, target: merge_requests.first),
+ create(:event, :created, author: user, target: merge_requests.second),
+ create(:event, :created, author: user, target: issue),
+ create(:event, :created, author: user, target: issue)
+ ]
+ end
+
+ it 'removes duplicated merge request create records' do
+ expect { migration.up }.to change { Event.count }.from(6).to(5)
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 86afa856ea7..767f0ad9e65 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1220,7 +1220,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path.parameterize, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index 5c5dcd9f5c9..d4433a88a15 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -4,6 +4,13 @@ describe ProtectableDropdown do
let(:project) { create(:project, :repository) }
let(:subject) { described_class.new(project, :branches) }
+ describe 'initialize' do
+ it 'raises ArgumentError for invalid ref type' do
+ expect { described_class.new(double, :foo) }
+ .to raise_error(ArgumentError, "invalid ref type `foo`")
+ end
+ end
+
describe '#protectable_ref_names' do
before do
project.protected_branches.create(name: 'master')
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 992a6e8d76a..dafe3f466a2 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -16,11 +16,13 @@ describe API::Commits do
end
describe 'GET /projects/:id/repository/commits' do
- context 'authorized user' do
+ let(:route) { "/projects/#{project_id}/repository/commits" }
+
+ shared_examples_for 'project commits' do
it "returns project commits" do
commit = project.repository.commit
- get api("/projects/#{project_id}/repository/commits", user)
+ get api(route, current_user)
expect(response).to have_http_status(200)
expect(response).to match_response_schema('public_api/v4/commits')
@@ -32,7 +34,7 @@ describe API::Commits do
it 'include correct pagination headers' do
commit_count = project.repository.count_commits(ref: 'master').to_s
- get api("/projects/#{project_id}/repository/commits", user)
+ get api(route, current_user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -40,140 +42,151 @@ describe API::Commits do
end
end
- context "unauthorized user" do
- it "does not return project commits" do
- get api("/projects/#{project_id}/repository/commits")
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'project commits'
+ end
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
end
- context "since optional parameter" do
- it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
- after = commits.second.created_at
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+ it_behaves_like 'project commits'
- expect(json_response.size).to eq 2
- expect(json_response.first["id"]).to eq(commits.first.id)
- expect(json_response.second["id"]).to eq(commits.second.id)
- end
+ context "since optional parameter" do
+ it "returns project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ after = commits.second.created_at
- it 'include correct pagination headers' do
- commits = project.repository.commits("master")
- after = commits.second.created_at
- commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
- get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ after = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
+
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context "until optional parameter" do
- it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
- before = commits.second.created_at
+ context "until optional parameter" do
+ it "returns project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
- get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
- expect(json_response.size).to eq(20)
- else
- expect(json_response.size).to eq(commits.size - 1)
- end
+ if commits.size >= 20
+ expect(json_response.size).to eq(20)
+ else
+ expect(json_response.size).to eq(commits.size - 1)
+ end
- expect(json_response.first["id"]).to eq(commits.second.id)
- expect(json_response.second["id"]).to eq(commits.third.id)
- end
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
- it 'include correct pagination headers' do
- commits = project.repository.commits("master")
- before = commits.second.created_at
- commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
- get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context "invalid xmlschema date parameters" do
- it "returns an invalid parameter error message" do
- get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
+ context "invalid xmlschema date parameters" do
+ it "returns an invalid parameter error message" do
+ get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
- expect(response).to have_http_status(400)
- expect(json_response['error']).to eq('since is invalid')
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('since is invalid')
+ end
end
- end
- context "path optional parameter" do
- it "returns project commits matching provided path parameter" do
- path = 'files/ruby/popen.rb'
- commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
+ context "path optional parameter" do
+ it "returns project commits matching provided path parameter" do
+ path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
- expect(json_response.size).to eq(3)
- expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- end
+ expect(json_response.size).to eq(3)
+ expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ end
- it 'include correct pagination headers' do
- path = 'files/ruby/popen.rb'
- commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
+ it 'include correct pagination headers' do
+ path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context 'with pagination params' do
- let(:page) { 1 }
- let(:per_page) { 5 }
- let(:ref_name) { 'master' }
- let!(:request) do
- get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
- end
+ context 'with pagination params' do
+ let(:page) { 1 }
+ let(:per_page) { 5 }
+ let(:ref_name) { 'master' }
+ let!(:request) do
+ get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
+ end
- it 'returns correct headers' do
- commit_count = project.repository.count_commits(ref: ref_name).to_s
+ it 'returns correct headers' do
+ commit_count = project.repository.count_commits(ref: ref_name).to_s
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eq('1')
- expect(response.headers['Link']).to match(/page=1&per_page=5/)
- expect(response.headers['Link']).to match(/page=2&per_page=5/)
- end
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eq('1')
+ expect(response.headers['Link']).to match(/page=1&per_page=5/)
+ expect(response.headers['Link']).to match(/page=2&per_page=5/)
+ end
- context 'viewing the first page' do
- it 'returns the first 5 commits' do
- commit = project.repository.commit
+ context 'viewing the first page' do
+ it 'returns the first 5 commits' do
+ commit = project.repository.commit
- expect(json_response.size).to eq(per_page)
- expect(json_response.first['id']).to eq(commit.id)
- expect(response.headers['X-Page']).to eq('1')
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('1')
+ end
end
- end
- context 'viewing the third page' do
- let(:page) { 3 }
+ context 'viewing the third page' do
+ let(:page) { 3 }
- it 'returns the third 5 commits' do
- commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
+ it 'returns the third 5 commits' do
+ commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
- expect(json_response.size).to eq(per_page)
- expect(json_response.first['id']).to eq(commit.id)
- expect(response.headers['X-Page']).to eq('3')
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('3')
+ end
end
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 55c998b13b8..ea97c556430 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -33,6 +33,15 @@ describe API::Files do
expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
end
+ it 'returns json when file has txt extension' do
+ file_path = "bar%2Fbranch-test.txt"
+
+ get api(route(file_path), current_user), params
+
+ expect(response).to have_http_status(200)
+ expect(response.content_type).to eq('application/json')
+ end
+
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
@@ -220,6 +229,7 @@ describe API::Files do
post api(route("new_file_with_author%2Etxt"), user), valid_params
expect(response).to have_http_status(201)
+ expect(response.content_type).to eq('application/json')
last_commit = project.repository.commit.raw
expect(last_commit.author_email).to eq(author_email)
expect(last_commit.author_name).to eq(author_name)
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index e4f9c47fb33..1aa8a95780e 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -96,7 +96,7 @@ describe API::ProtectedBranches do
describe 'POST /projects/:id/protected_branches' do
let(:branch_name) { 'new_branch' }
- context 'when authenticated as a master' do
+ context 'when authenticated as a master' do
before do
project.add_master(user)
end
@@ -221,7 +221,7 @@ describe API::ProtectedBranches do
context 'when branch has a wildcard in its name' do
let(:protected_name) { 'feature*' }
-
+
it "unprotects a wildcard branch" do
delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 97275b80d03..737c028ad53 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -45,7 +45,7 @@ describe API::Settings, 'Settings' do
help_page_hide_commercial_content: true,
help_page_support_url: 'http://example.com/help',
project_export_enabled: false
-
+
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['password_authentication_enabled']).to be_falsey
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index ebd67eb1e94..7ccba4ba3ec 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -130,7 +130,7 @@ describe Ci::API::Builds do
register_builds info: { platform: :darwin }
expect(response).to have_http_status(201)
-
+
expect(json_response["options"]).to be_empty
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 8fef480274d..a1f3bec42cc 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -48,6 +48,16 @@ describe MergeRequests::CreateService do
expect(Todo.where(attributes).count).to be_zero
end
+ it 'creates exactly 1 create MR event' do
+ attributes = {
+ action: Event::CREATED,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name
+ }
+
+ expect(Event.where(attributes).count).to eq(1)
+ end
+
context 'when merge request is assigned to someone' do
let(:opts) do
{
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 365cb6b8f09..0726e135b20 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -144,7 +144,7 @@ describe WebHookService do
describe '#async_execute' do
let(:system_hook) { create(:system_hook) }
-
+
it 'enqueue WebHookWorker' do
expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index d21c4324d9e..99b8b6b7ea4 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -54,8 +54,8 @@ module FilteredSearchHelpers
# Iterates through each visual token inside
# .tokens-container to make sure the correct names and values are rendered
def expect_tokens(tokens)
- page.find '.filtered-search-box .tokens-container' do
- page.all(:css, '.tokens-container li').each_with_index do |el, index|
+ page.within '.filtered-search-box .tokens-container' do
+ page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index|
token_name = tokens[index][:name]
token_value = tokens[index][:value]
@@ -67,6 +67,28 @@ module FilteredSearchHelpers
end
end
+ def create_token(token_name, token_value = nil, symbol = nil)
+ { name: token_name, value: "#{symbol}#{token_value}" }
+ end
+
+ def author_token(author_name = nil)
+ create_token('Author', author_name)
+ end
+
+ def assignee_token(assignee_name = nil)
+ create_token('Assignee', assignee_name)
+ end
+
+ def milestone_token(milestone_name = nil, has_symbol = true)
+ symbol = has_symbol ? '%' : nil
+ create_token('Milestone', milestone_name, symbol)
+ end
+
+ def label_token(label_name = nil, has_symbol = true)
+ symbol = has_symbol ? '~' : nil
+ create_token('Label', label_name, symbol)
+ end
+
def default_placeholder
'Search or filter results...'
end
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
index c89389b90ca..ef3c8e7087f 100755
--- a/spec/support/generate-seed-repo-rb
+++ b/spec/support/generate-seed-repo-rb
@@ -1,16 +1,16 @@
#!/usr/bin/env ruby
-#
+#
# # generate-seed-repo-rb
-#
+#
# This script generates the seed_repo.rb file used by lib/gitlab/git
# tests. The seed_repo.rb file needs to be updated anytime there is a
# Git push to https://gitlab.com/gitlab-org/gitlab-git-test.
-#
+#
# Usage:
-#
+#
# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb
-#
-#
+#
+#
require 'erb'
require 'tempfile'
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index ff60bd0c0ae..bb6b7c63ee9 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -1,6 +1,6 @@
# AccessMatchersForController
#
-# For testing authorize_xxx in controller.
+# For testing authorize_xxx in controller.
module AccessMatchersForController
extend RSpec::Matchers::DSL
include Warden::Test::Helpers
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 37c89d37aa0..45c10e78789 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -39,14 +39,17 @@ module StubConfiguration
end
def stub_storage_settings(messages)
+ # Default storage is always required
+ messages['default'] ||= Gitlab.config.repositories.storages.default
messages.each do |storage_name, storage_settings|
+ storage_settings['path'] ||= TestEnv.repos_path
storage_settings['failure_count_threshold'] ||= 10
storage_settings['failure_wait_time'] ||= 30
storage_settings['failure_reset_time'] ||= 1800
storage_settings['storage_timeout'] ||= 5
end
- allow(Gitlab.config.repositories).to receive(:storages).and_return(messages)
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages))
end
private
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index b29d63c7d67..1e9b20435ec 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -89,7 +89,7 @@ describe 'gitlab:gitaly namespace rake task' do
it 'calls make in the gitaly directory without BUNDLE_PATH' do
expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
-
+
run_rake_task('gitlab:gitaly:install', clone_path)
end
end
diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz
new file mode 100644
index 00000000000..6353f6605d5
--- /dev/null
+++ b/vendor/project_templates/express.tar.gz
Binary files differ
diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz
new file mode 100644
index 00000000000..d7c0ab74d01
--- /dev/null
+++ b/vendor/project_templates/spring.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index c9e1b630a9e..5fc28f8b5ba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6275,9 +6275,9 @@ webpack-stats-plugin@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9"
-webpack@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63"
+webpack@^3.5.4:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.4.tgz#5583eb263ed27b78b5bd17bfdfb0eb1b1cd1bf81"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"