summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/application.js185
-rw-r--r--app/assets/javascripts/boards/components/board_card.js69
-rw-r--r--app/assets/javascripts/boards/components/board_card.js.es661
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es64
-rw-r--r--app/assets/javascripts/boards/models/list.js.es612
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js.es65
-rw-r--r--app/assets/javascripts/build.js35
-rw-r--r--app/assets/javascripts/commit/image_file.js81
-rw-r--r--app/assets/javascripts/dispatcher.js.es65
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es650
-rw-r--r--app/assets/javascripts/files_comment_button.js71
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es66
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js8
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js18
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js201
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js542
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js265
-rw-r--r--app/assets/javascripts/issuable.js.es62
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es653
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js10
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es64
-rw-r--r--app/assets/javascripts/milestone.js1
-rw-r--r--app/assets/javascripts/milestone_select.js5
-rw-r--r--app/assets/javascripts/new_branch_form.js32
-rw-r--r--app/assets/javascripts/profile/profile.js.es67
-rw-r--r--app/assets/javascripts/profile/profile_bundle.js5
-rw-r--r--app/assets/javascripts/project.js2
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js.es69
-rw-r--r--app/assets/javascripts/protected_branches/protected_branches_bundle.js8
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js4
-rw-r--r--app/assets/javascripts/task_list.js12
-rw-r--r--app/assets/javascripts/user_callout.js58
-rw-r--r--app/assets/javascripts/users/users_bundle.js4
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es696
-rw-r--r--app/assets/javascripts/vue_pipelines_index/time_ago.js.es65
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss46
-rw-r--r--app/assets/stylesheets/framework/jquery.scss11
-rw-r--r--app/assets/stylesheets/highlight/dark.scss13
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss13
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss13
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss13
-rw-r--r--app/assets/stylesheets/highlight/white.scss11
-rw-r--r--app/assets/stylesheets/pages/diff.scss8
-rw-r--r--app/assets/stylesheets/pages/notes.scss51
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss207
-rw-r--r--app/assets/stylesheets/pages/profile.scss38
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/controllers/concerns/issuable_actions.rb17
-rw-r--r--app/controllers/concerns/service_params.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb23
-rw-r--r--app/helpers/builds_helper.rb7
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/models/concerns/uniquify.rb30
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/namespace.rb10
-rw-r--r--app/models/project.rb20
-rw-r--r--app/models/project_services/mattermost_service.rb14
-rw-r--r--app/models/project_services/mock_ci_service.rb82
-rw-r--r--app/models/project_services/slack_service.rb14
-rw-r--r--app/models/repository.rb217
-rw-r--r--app/models/service.rb5
-rw-r--r--app/models/user.rb57
-rw-r--r--app/policies/user_policy.rb8
-rw-r--r--app/services/files/create_dir_service.rb2
-rw-r--r--app/services/files/create_service.rb7
-rw-r--r--app/services/files/destroy_service.rb2
-rw-r--r--app/services/files/multi_service.rb33
-rw-r--r--app/services/files/update_service.rb4
-rw-r--r--app/services/groups/destroy_service.rb3
-rw-r--r--app/services/users/destroy_service.rb21
-rw-r--r--app/uploaders/artifact_uploader.rb4
-rw-r--r--app/uploaders/attachment_uploader.rb2
-rw-r--r--app/uploaders/avatar_uploader.rb2
-rw-r--r--app/uploaders/file_uploader.rb25
-rw-r--r--app/uploaders/gitlab_uploader.rb10
-rw-r--r--app/uploaders/uploader_helper.rb6
-rw-r--r--app/views/admin/runners/_runner.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml1
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml5
-rw-r--r--app/views/dashboard/_activities.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/groups/_activities.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml5
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml2
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml4
-rw-r--r--app/views/projects/boards/_show.html.haml1
-rw-r--r--app/views/projects/boards/components/_card.html.haml10
-rw-r--r--app/views/projects/branches/new.html.haml8
-rw-r--r--app/views/projects/builds/_header.html.haml9
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml92
-rw-r--r--app/views/projects/diffs/_file_header.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/pipelines/new.html.haml6
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--app/views/shared/icons/_icon_customization.svg1
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/users/show.html.haml1
-rw-r--r--changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml4
-rw-r--r--changelogs/unreleased/14748-runner-version-in-admin-views.yml4
-rw-r--r--changelogs/unreleased/1937-https-clone-url-username.yml4
-rw-r--r--changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml4
-rw-r--r--changelogs/unreleased/25920-create-issue-from-failing-build.yml4
-rw-r--r--changelogs/unreleased/27267-events-project-query-performance-regression.yml4
-rw-r--r--changelogs/unreleased/27840-improve-search-bar-experience.yml4
-rw-r--r--changelogs/unreleased/28212-avoid-dos-on-build-trace.yml4
-rw-r--r--changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml4
-rw-r--r--changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml4
-rw-r--r--changelogs/unreleased/28723-consistent-handling-indexof.yml4
-rw-r--r--changelogs/unreleased/api-remove-owned-groups.yml4
-rw-r--r--changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml4
-rw-r--r--changelogs/unreleased/fix-mr-size-with-over-100-files.yml4
-rw-r--r--changelogs/unreleased/issue_24815.yml4
-rw-r--r--changelogs/unreleased/mock-ci-service.yml4
-rw-r--r--changelogs/unreleased/moving-issue-with-two-list-labels.yml4
-rw-r--r--changelogs/unreleased/mr-diff-comment-button.yml4
-rw-r--r--changelogs/unreleased/remove-jquery-ui-plugins.yml4
-rw-r--r--changelogs/unreleased/rss-btn-alignment-fix.yml4
-rw-r--r--changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml4
-rw-r--r--changelogs/unreleased/ssh-key-paste.yml4
-rw-r--r--changelogs/unreleased/unified-member-api-response.yml4
-rw-r--r--changelogs/unreleased/update-vue-2-1.yml4
-rw-r--r--changelogs/unreleased/user-callouts.yml4
-rw-r--r--config/webpack.config.js2
-rw-r--r--db/fixtures/development/17_cycle_analytics.rb10
-rw-r--r--db/fixtures/development/19_nested_groups.rb69
-rw-r--r--db/migrate/20160610201627_migrate_users_notification_level.rb2
-rw-r--r--db/migrate/20170206115204_add_column_ghost_to_users.rb11
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/container_registry.md40
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md2
-rw-r--r--doc/administration/pages/index.md23
-rw-r--r--doc/administration/reply_by_email.md4
-rw-r--r--doc/api/groups.md15
-rw-r--r--doc/api/services.md35
-rw-r--r--doc/api/v3_to_v4.md3
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/ci_setup.md3
-rw-r--r--doc/development/ux_guide/users.md34
-rw-r--r--doc/integration/auth0.md2
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md6
-rw-r--r--doc/user/project/integrations/img/mattermost_configuration.pngbin73502 -> 249592 bytes
-rw-r--r--doc/user/project/integrations/img/slack_configuration.pngbin29825 -> 229050 bytes
-rw-r--r--doc/user/project/integrations/mattermost.md11
-rw-r--r--doc/user/project/integrations/mock_ci.md13
-rw-r--r--doc/user/project/integrations/slack.md10
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md6
-rw-r--r--features/project/commits/branches.feature8
-rw-r--r--features/steps/explore/projects.rb2
-rw-r--r--features/steps/project/commits/branches.rb24
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/commits.rb7
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/groups.rb16
-rw-r--r--lib/api/helpers/runner.rb23
-rw-r--r--lib/api/members.rb7
-rw-r--r--lib/api/runner.rb52
-rw-r--r--lib/api/services.rb15
-rw-r--r--lib/api/triggers.rb11
-rw-r--r--lib/api/v3/commits.rb7
-rw-r--r--lib/api/v3/groups.rb38
-rw-r--r--lib/banzai/filter/image_link_filter.rb9
-rw-r--r--lib/gitlab/git/blob.rb157
-rw-r--r--lib/gitlab/git/index.rb126
-rw-r--r--lib/gitlab/git/repository.rb91
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/middleware/webpack_proxy.rb12
-rw-r--r--lib/gitlab/regex.rb11
-rw-r--r--package.json2
-rw-r--r--spec/controllers/blob_controller_spec.rb8
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb4
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb12
-rw-r--r--spec/controllers/projects/boards/issues_controller_spec.rb6
-rw-r--r--spec/controllers/projects/boards/lists_controller_spec.rb10
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb8
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb28
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb52
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb8
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb32
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb8
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb8
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb12
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb12
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb18
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb66
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb16
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb70
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb8
-rw-r--r--spec/controllers/projects/protected_branches_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb8
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb6
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb6
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb36
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb4
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb12
-rw-r--r--spec/controllers/projects/todo_controller_spec.rb8
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb6
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb8
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb8
-rw-r--r--spec/controllers/projects_controller_spec.rb72
-rw-r--r--spec/factories/projects.rb15
-rw-r--r--spec/factories/users.rb5
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/issues_spec.rb32
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb12
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/issuable_templates_spec.rb25
-rw-r--r--spec/features/projects/members/sorting_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb33
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb26
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb24
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb22
-rw-r--r--spec/features/user_callout_spec.rb37
-rw-r--r--spec/helpers/application_helper_spec.rb4
-rw-r--r--spec/javascripts/boards/board_card_spec.js168
-rw-r--r--spec/javascripts/boards/list_spec.js.es621
-rw-r--r--spec/javascripts/fixtures/branches.rb2
-rw-r--r--spec/javascripts/fixtures/builds.rb2
-rw-r--r--spec/javascripts/fixtures/issues.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb2
-rw-r--r--spec/javascripts/fixtures/projects.rb2
-rw-r--r--spec/javascripts/fixtures/todos.rb4
-rw-r--r--spec/javascripts/fixtures/user_callout.html.haml2
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_graph_spec.js8
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js3
-rw-r--r--spec/javascripts/graphs/stat_graph_spec.js20
-rw-r--r--spec/javascripts/user_callout_spec.js.es637
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb10
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb4
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb185
-rw-r--r--spec/lib/gitlab/git/index_spec.rb220
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb199
-rw-r--r--spec/lib/gitlab/git_access_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb4
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb4
-rw-r--r--spec/models/concerns/routable_spec.rb4
-rw-r--r--spec/models/concerns/uniquify_spec.rb33
-rw-r--r--spec/models/cycle_analytics/production_spec.rb5
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb5
-rw-r--r--spec/models/namespace_spec.rb4
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb39
-rw-r--r--spec/models/repository_spec.rb95
-rw-r--r--spec/models/user_spec.rb55
-rw-r--r--spec/policies/user_policy_spec.rb37
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/files_spec.rb4
-rw-r--r--spec/requests/api/groups_spec.rb16
-rw-r--r--spec/requests/api/members_spec.rb10
-rw-r--r--spec/requests/api/runner_spec.rb148
-rw-r--r--spec/requests/api/v3/commits_spec.rb2
-rw-r--r--spec/requests/api/v3/files_spec.rb4
-rw-r--r--spec/requests/api/v3/groups_spec.rb35
-rw-r--r--spec/services/groups/destroy_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolve_service_spec.rb5
-rw-r--r--spec/services/users/destroy_spec.rb48
-rw-r--r--spec/support/cycle_analytics_helpers.rb14
-rw-r--r--spec/support/dropzone_helper.rb37
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb2
-rw-r--r--spec/support/markdown_feature.rb4
-rw-r--r--spec/support/test_env.rb4
-rw-r--r--spec/support/update_invalid_issuable.rb57
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb7
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb7
-rw-r--r--spec/uploaders/file_uploader_spec.rb46
-rw-r--r--spec/uploaders/uploader_helper_spec.rb35
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb42
-rw-r--r--spec/workers/repository_fork_worker_spec.rb20
-rw-r--r--yarn.lock6
291 files changed, 4159 insertions, 2640 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f5b101ad6b..c039335c46d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -182,6 +182,12 @@ entry.
- Remove deprecated GitlabCiService.
- Requeue pending deletion projects.
+## 8.16.7 (2017-02-27)
+
+- No changes.
+- No changes.
+- Fix MR changes tab size count when there are over 100 files in the diff.
+
## 8.16.6 (2017-02-17)
- API: Fix file downloading. !0 (8267)
diff --git a/Gemfile b/Gemfile
index 8715acba24f..a3d036792e8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
-gem 'omniauth', '~> 1.3.2'
+gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index b46b09c7544..5ff18442c4f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -328,7 +328,7 @@ GEM
temple (~> 0.7.6)
thor
tilt
- hashie (3.4.4)
+ hashie (3.5.5)
health_check (2.2.1)
rails (>= 4.0)
hipchat (1.5.2)
@@ -441,7 +441,7 @@ GEM
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
- omniauth (1.3.2)
+ omniauth (1.4.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
@@ -920,7 +920,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
- omniauth (~> 1.3.2)
+ omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 8e468faedbf..c51860d1604 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -6,13 +6,7 @@
/* global AwardsHandler */
/* global Aside */
-function requireAll(context) { return context.keys().map(context); }
-
window.$ = window.jQuery = require('jquery');
-require('jquery-ui/ui/autocomplete');
-require('jquery-ui/ui/draggable');
-require('jquery-ui/ui/effect-highlight');
-require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
@@ -46,15 +40,176 @@ require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable');
require('./shortcuts_network');
require('vendor/jquery.nicescroll');
-requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
+
+// behaviors
+require('./behaviors/autosize');
+require('./behaviors/details_behavior');
+require('./behaviors/quick_submit');
+require('./behaviors/requires_input');
+require('./behaviors/toggler_behavior');
+
+// blob
+require('./blob/blob_ci_yaml');
+require('./blob/blob_dockerfile_selector');
+require('./blob/blob_dockerfile_selectors');
+require('./blob/blob_file_dropzone');
+require('./blob/blob_gitignore_selector');
+require('./blob/blob_gitignore_selectors');
+require('./blob/blob_license_selector');
+require('./blob/blob_license_selectors');
+require('./blob/template_selector');
+
+// templates
+require('./templates/issuable_template_selector');
+require('./templates/issuable_template_selectors');
+
+// commit
+require('./commit/file.js');
+require('./commit/image_file.js');
+
+// extensions
+require('./extensions/array');
+require('./extensions/custom_event');
+require('./extensions/element');
+require('./extensions/jquery');
+require('./extensions/object');
+
+// lib/utils
+require('./lib/utils/animate');
+require('./lib/utils/bootstrap_linked_tabs');
+require('./lib/utils/common_utils');
+require('./lib/utils/datetime_utility');
+require('./lib/utils/notify');
+require('./lib/utils/pretty_time');
+require('./lib/utils/text_utility');
+require('./lib/utils/type_utility');
+require('./lib/utils/url_utility');
+
+// u2f
+require('./u2f/authenticate');
+require('./u2f/error');
+require('./u2f/register');
+require('./u2f/util');
+
+// droplab
+require('./droplab/droplab');
+require('./droplab/droplab_ajax');
+require('./droplab/droplab_ajax_filter');
+require('./droplab/droplab_filter');
+
+// everything else
+require('./abuse_reports');
+require('./activities');
+require('./admin');
+require('./ajax_loading_spinner');
+require('./api');
+require('./aside');
+require('./autosave');
+require('./awards_handler');
+require('./breakpoints');
+require('./broadcast_message');
+require('./build');
+require('./build_artifacts');
+require('./build_variables');
+require('./ci_lint_editor');
+require('./commit');
+require('./commits');
+require('./compare');
+require('./compare_autocomplete');
+require('./confirm_danger_modal');
+require('./copy_as_gfm');
+require('./copy_to_clipboard');
+require('./create_label');
+require('./diff');
+require('./dispatcher');
+require('./dropzone_input');
+require('./due_date_select');
+require('./files_comment_button');
+require('./flash');
+require('./gfm_auto_complete');
+require('./gl_dropdown');
+require('./gl_field_error');
+require('./gl_field_errors');
+require('./gl_form');
+require('./group_avatar');
+require('./group_label_subscription');
+require('./groups_select');
+require('./header');
+require('./importer_status');
+require('./issuable');
+require('./issuable_context');
+require('./issuable_form');
+require('./issue');
+require('./issue_status_select');
+require('./issues_bulk_assignment');
+require('./label_manager');
+require('./labels');
+require('./labels_select');
+require('./layout_nav');
+require('./line_highlighter');
+require('./logo');
+require('./member_expiration_date');
+require('./members');
+require('./merge_request');
+require('./merge_request_tabs');
+require('./merge_request_widget');
+require('./merged_buttons');
+require('./milestone');
+require('./milestone_select');
+require('./mini_pipeline_graph_dropdown');
+require('./namespace_select');
+require('./new_branch_form');
+require('./new_commit_form');
+require('./notes');
+require('./notifications_dropdown');
+require('./notifications_form');
+require('./pager');
+require('./pipelines');
+require('./preview_markdown');
+require('./project');
+require('./project_avatar');
+require('./project_find_file');
+require('./project_fork');
+require('./project_import');
+require('./project_label_subscription');
+require('./project_new');
+require('./project_select');
+require('./project_show');
+require('./project_variables');
+require('./projects_list');
+require('./render_gfm');
+require('./render_math');
+require('./right_sidebar');
+require('./search');
+require('./search_autocomplete');
+require('./shortcuts');
+require('./shortcuts_blob');
+require('./shortcuts_dashboard_navigation');
+require('./shortcuts_find_file');
+require('./shortcuts_issuable');
+require('./shortcuts_navigation');
+require('./shortcuts_network');
+require('./signin_tabs_memoizer');
+require('./single_file_diff');
+require('./smart_interval');
+require('./snippets_list');
+require('./star');
+require('./subbable_resource');
+require('./subscription');
+require('./subscription_select');
+require('./syntax_highlight');
+require('./task_list');
+require('./todos');
+require('./tree');
+require('./user');
+require('./user_tabs');
+require('./username_validator');
+require('./users_select');
+require('./version_check_image');
+require('./visibility_select');
+require('./wikis');
+require('./zen_mode');
+
require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill();
diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js
new file mode 100644
index 00000000000..52f61d84517
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_card.js
@@ -0,0 +1,69 @@
+/* global Vue */
+require('./issue_card_inner');
+
+const Store = gl.issueBoards.BoardsStore;
+
+module.exports = {
+ name: 'BoardsIssueCard',
+ template: `
+ <li class="card"
+ :class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
+ :index="index"
+ :data-issue-id="issue.id"
+ @mousedown="mouseDown"
+ @mousemove="mouseMove"
+ @mouseup="showIssue($event)">
+ <issue-card-inner
+ :list="list"
+ :issue="issue"
+ :issue-link-base="issueLinkBase"
+ :root-path="rootPath" />
+ </li>
+ `,
+ components: {
+ 'issue-card-inner': gl.issueBoards.IssueCardInner,
+ },
+ props: {
+ list: Object,
+ issue: Object,
+ issueLinkBase: String,
+ disabled: Boolean,
+ index: Number,
+ rootPath: String,
+ },
+ data() {
+ return {
+ showDetail: false,
+ detailIssue: Store.detail,
+ };
+ },
+ computed: {
+ issueDetailVisible() {
+ return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
+ },
+ },
+ methods: {
+ mouseDown() {
+ this.showDetail = true;
+ },
+ mouseMove() {
+ this.showDetail = false;
+ },
+ showIssue(e) {
+ const targetTagName = e.target.tagName.toLowerCase();
+
+ if (targetTagName === 'a' || targetTagName === 'button') return;
+
+ if (this.showDetail) {
+ this.showDetail = false;
+
+ if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
+ Store.detail.issue = {};
+ } else {
+ Store.detail.issue = this.issue;
+ Store.detail.list = this.list;
+ }
+ }
+ },
+ },
+};
diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6
deleted file mode 100644
index 0ea66bd027c..00000000000
--- a/app/assets/javascripts/boards/components/board_card.js.es6
+++ /dev/null
@@ -1,61 +0,0 @@
-/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
-/* global Vue */
-
-require('./issue_card_inner');
-
-(() => {
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
-
- gl.issueBoards.BoardCard = Vue.extend({
- template: '#js-board-list-card',
- components: {
- 'issue-card-inner': gl.issueBoards.IssueCardInner,
- },
- props: {
- list: Object,
- issue: Object,
- issueLinkBase: String,
- disabled: Boolean,
- index: Number,
- rootPath: String,
- },
- data () {
- return {
- showDetail: false,
- detailIssue: Store.detail
- };
- },
- computed: {
- issueDetailVisible () {
- return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
- }
- },
- methods: {
- mouseDown () {
- this.showDetail = true;
- },
- mouseMove() {
- this.showDetail = false;
- },
- showIssue (e) {
- const targetTagName = e.target.tagName.toLowerCase();
-
- if (targetTagName === 'a' || targetTagName === 'button') return;
-
- if (this.showDetail) {
- this.showDetail = false;
-
- if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
- Store.detail.issue = {};
- } else {
- Store.detail.issue = this.issue;
- Store.detail.list = this.list;
- }
- }
- }
- }
- });
-})();
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 60b0a30af3f..d92047cc0f8 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -2,7 +2,7 @@
/* global Vue */
/* global Sortable */
-require('./board_card');
+const boardCard = require('./board_card');
require('./board_new_issue');
(() => {
@@ -14,7 +14,7 @@ require('./board_new_issue');
gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: {
- 'board-card': gl.issueBoards.BoardCard,
+ boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
},
props: {
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index 5152be56b66..8158ed4ec2c 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -123,14 +123,18 @@ class List {
if (listFrom) {
this.issuesSize += 1;
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
- .then(() => {
- listFrom.getIssues(false);
- });
+ this.updateIssueLabel(issue, listFrom);
}
}
}
+ updateIssueLabel(issue, listFrom) {
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+ .then(() => {
+ listFrom.getIssues(false);
+ });
+ }
+
findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0];
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 50842ecbaaa..56436c8fdc7 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -92,9 +92,12 @@
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
- // Add to new lists issues if it doesn't already exist
if (!issueTo) {
+ // Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
+ } else {
+ listTo.updateIssueLabel(issue, listFrom);
+ issueTo.removeLabel(listFrom.label);
}
if (listTo.type === 'done') {
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 8fa1aceddff..6e6e9b18686 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -7,7 +7,7 @@
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
- Build.interval = null;
+ Build.timeout = null;
Build.state = null;
@@ -31,7 +31,7 @@
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
- clearInterval(Build.interval);
+ clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
@@ -52,17 +52,7 @@
this.getInitialBuildTrace();
this.initScrollButtonAffix();
}
- if (this.buildStatus === "running" || this.buildStatus === "pending") {
- Build.interval = setInterval((function(_this) {
- // Check for new build output if user still watching build page
- // Only valid for runnig build when output changes during time
- return function() {
- if (_this.location() === _this.pageUrl) {
- return _this.getBuildTrace();
- }
- };
- })(this), 4000);
- }
+ this.invokeBuildTrace();
}
Build.prototype.initSidebar = function() {
@@ -75,6 +65,22 @@
return window.location.href.split("#")[0];
};
+ Build.prototype.invokeBuildTrace = function() {
+ var continueRefreshStatuses = ['running', 'pending'];
+ // Continue to update build trace when build is running or pending
+ if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
+ // Check for new build output if user still watching build page
+ // Only valid for runnig build when output changes during time
+ Build.timeout = setTimeout((function(_this) {
+ return function() {
+ if (_this.location() === _this.pageUrl) {
+ return _this.getBuildTrace();
+ }
+ };
+ })(this), 4000);
+ }
+ };
+
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
@@ -86,7 +92,7 @@
if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height());
}
- if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
+ if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
}
@@ -105,6 +111,7 @@
if (log.state) {
_this.state = log.state;
}
+ _this.invokeBuildTrace();
if (log.status === "running") {
if (log.append) {
$('.js-build-output').append(log.html);
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 49bb64a3472..17d14dc1e79 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -52,6 +52,30 @@
return this.views[viewMode].call(this);
};
+ ImageFile.prototype.initDraggable = function($el, padding, callback) {
+ var dragging = false;
+ var $body = $('body');
+ var $offsetEl = $el.parent();
+
+ $el.off('mousedown').on('mousedown', function() {
+ dragging = true;
+ $body.css('user-select', 'none');
+ });
+
+ $body.off('mouseup').off('mousemove').on('mouseup', function() {
+ dragging = false;
+ $body.css('user-select', '');
+ })
+ .on('mousemove', function(e) {
+ var left;
+ if (!dragging) return;
+
+ left = e.pageX - ($offsetEl.offset().left + padding);
+
+ callback(e, left);
+ });
+ };
+
prepareFrames = function(view) {
var maxHeight, maxWidth;
maxWidth = 0;
@@ -96,26 +120,30 @@
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
- var ref;
+ var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
- $('.swipe-frame', view).css({
+ $swipeFrame = $('.swipe-frame', view);
+ $swipeWrap = $('.swipe-wrap', view);
+ $swipeBar = $('.swipe-bar', view);
+
+ $swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
- $('.swipe-wrap', view).css({
+ $swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
- return $('.swipe-bar', view).css({
+ $swipeBar.css({
left: 0
- }).draggable({
- axis: 'x',
- containment: 'parent',
- drag: function(event) {
- return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
- },
- stop: function(event) {
- return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+ });
+
+ wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($swipeBar, wrapPadding, function(e, left) {
+ if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
+ $swipeWrap.width((maxWidth + 1) - left);
+ $swipeBar.css('left', left);
}
});
};
@@ -128,9 +156,14 @@
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
- var ref;
+ var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
- $('.onion-skin-frame', view).css({
+ $frame = $('.onion-skin-frame', view);
+ $frameAdded = $('.frame.added', view);
+ $track = $('.drag-track', view);
+ $dragger = $('.dragger', $track);
+
+ $frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
@@ -138,16 +171,18 @@
width: maxWidth + 1,
height: maxHeight + 2
});
- return $('.dragger', view).css({
+ $dragger.css({
left: dragTrackWidth
- }).draggable({
- axis: 'x',
- containment: 'parent',
- drag: function(event) {
- return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
- },
- stop: function(event) {
- return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+ });
+
+ framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($dragger, framePadding, function(e, left) {
+ var opacity = left / dragTrackWidth;
+
+ if (opacity >= 0 && opacity <= 1) {
+ $dragger.css('left', left);
+ $frameAdded.css('opacity', opacity);
}
});
};
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 089ecedeb78..0f678492d4c 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -36,6 +36,7 @@
/* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob');
+const UserCallout = require('./user_callout');
(function() {
var Dispatcher;
@@ -277,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show':
new gl.CILintEditor();
break;
+ case 'users:show':
+ new UserCallout();
+ break;
}
switch (path.first()) {
case 'sessions':
@@ -313,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
+ new UserCallout();
break;
case 'profiles':
new NotificationsForm();
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index ad9d1d21a79..5d780bddb0e 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -503,32 +503,30 @@ module.exports = Vue.component('environment-item', {
</span>
</td>
- <td class="hidden-xs">
- <div v-if="!model.isFolder">
- <div class="btn-group" role="group">
- <actions-component v-if="hasManualActions && canCreateDeployment"
- :play-icon-svg="playIconSvg"
- :actions="manualActions">
- </actions-component>
-
- <external-url-component v-if="externalURL && canReadEnvironment"
- :external-url="externalURL">
- </external-url-component>
-
- <stop-component v-if="hasStopAction && canCreateDeployment"
- :stop-url="model.stop_path">
- </stop-component>
-
- <terminal-button-component v-if="model && model.terminal_path"
- :terminal-icon-svg="terminalIconSvg"
- :terminal-path="model.terminal_path">
- </terminal-button-component>
-
- <rollback-component v-if="canRetry && canCreateDeployment"
- :is-last-deployment="isLastDeployment"
- :retry-url="retryUrl">
- </rollback-component>
- </div>
+ <td class="hidden-xs environments-actions">
+ <div v-if="!model.isFolder" class="btn-group pull-right" role="group">
+ <actions-component v-if="hasManualActions && canCreateDeployment"
+ :play-icon-svg="playIconSvg"
+ :actions="manualActions">
+ </actions-component>
+
+ <external-url-component v-if="externalURL && canReadEnvironment"
+ :external-url="externalURL">
+ </external-url-component>
+
+ <stop-component v-if="hasStopAction && canCreateDeployment"
+ :stop-url="model.stop_path">
+ </stop-component>
+
+ <terminal-button-component v-if="model && model.terminal_path"
+ :terminal-icon-svg="terminalIconSvg"
+ :terminal-path="model.terminal_path">
+ </terminal-button-component>
+
+ <rollback-component v-if="canRetry && canCreateDeployment"
+ :is-last-deployment="isLastDeployment"
+ :retry-url="retryUrl">
+ </rollback-component>
</div>
</td>
</tr>
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 698870d0ce1..6d86888dcb8 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -1,16 +1,16 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global FilesCommentButton */
+/* global notes */
(function() {
+ let $commentButtonTemplate;
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.FilesCommentButton = (function() {
- var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
+ var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
COMMENT_BUTTON_CLASS = '.add-diff-note';
- COMMENT_BUTTON_TEMPLATE = _.template('<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
-
LINE_HOLDER_CLASS = '.line_holder';
LINE_NUMBER_CLASS = 'diff-line-num';
@@ -27,26 +27,29 @@
TEXT_FILE_SELECTOR = '.text-file';
- DEBOUNCE_TIMEOUT_DURATION = 100;
-
function FilesCommentButton(filesContainerElement) {
- var debounce;
- this.filesContainerElement = filesContainerElement;
- this.destroy = bind(this.destroy, this);
this.render = bind(this.render, this);
- this.VIEW_TYPE = $('input#view[type=hidden]').val();
- debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION);
- $(this.filesContainerElement).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy);
+ this.hideButton = bind(this.hideButton, this);
+ this.isParallelView = notes.isParallelView();
+ filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
+ .on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
}
FilesCommentButton.prototype.render = function(e) {
- var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
+ var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
$currentTarget = $(e.currentTarget);
-
- buttonParentElement = this.getButtonParent($currentTarget);
- if (!this.validateButtonParent(buttonParentElement)) return;
lineContentElement = this.getLineContent($currentTarget);
- if (!this.validateLineContent(lineContentElement)) return;
+ buttonParentElement = this.getButtonParent($currentTarget);
+
+ if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
+
+ $button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
+ buttonParentElement.addClass('is-over')
+ .nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
+
+ if ($button.length) {
+ return;
+ }
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({
@@ -61,19 +64,16 @@
}));
};
- FilesCommentButton.prototype.destroy = function(e) {
- if (this.isMovingToSameType(e)) {
- return;
- }
- $(COMMENT_BUTTON_CLASS, this.getButtonParent($(e.currentTarget))).remove();
+ FilesCommentButton.prototype.hideButton = function(e) {
+ var $currentTarget = $(e.currentTarget);
+ var buttonParentElement = this.getButtonParent($currentTarget);
+
+ buttonParentElement.removeClass('is-over')
+ .nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
};
FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
- var initializedButtonTemplate;
- initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE({
- COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr(1)
- });
- return $(initializedButtonTemplate).attr({
+ return $commentButtonTemplate.clone().attr({
'data-noteable-type': buttonAttributes.noteableType,
'data-noteable-id': buttonAttributes.noteableID,
'data-commit-id': buttonAttributes.commitID,
@@ -86,14 +86,14 @@
};
FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
- return $(hoveredElement.closest(TEXT_FILE_SELECTOR));
+ return hoveredElement.closest(TEXT_FILE_SELECTOR);
};
FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
return hoveredElement;
}
- if (this.VIEW_TYPE === 'inline') {
+ if (!this.isParallelView) {
return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
} else {
return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
@@ -101,7 +101,7 @@
};
FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
- if (this.VIEW_TYPE === 'inline') {
+ if (!this.isParallelView) {
if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
return hoveredElement;
}
@@ -114,17 +114,8 @@
}
};
- FilesCommentButton.prototype.isMovingToSameType = function(e) {
- var newButtonParent;
- newButtonParent = this.getButtonParent($(e.toElement));
- if (!newButtonParent) {
- return false;
- }
- return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
- };
-
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
- return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
+ return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
};
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
@@ -135,6 +126,8 @@
})();
$.fn.filesCommentButton = function() {
+ $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+
if (!(this && (this.parent().data('can-create-note') != null))) {
return;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index fbc72a3001a..dd565da507e 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -48,7 +48,11 @@
}
setOffset(offset = 0) {
- this.dropdown.style.left = `${offset}px`;
+ if (window.innerWidth > 480) {
+ this.dropdown.style.left = `${offset}px`;
+ } else {
+ this.dropdown.style.left = '0px';
+ }
}
renderContent(forceShowList = false) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index a01662e2f9e..9e6ed06054b 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -63,7 +63,7 @@
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
- return BLUR_KEYCODES.indexOf(keyCode) >= 0;
+ return BLUR_KEYCODES.indexOf(keyCode) !== -1;
};
GitLabDropdownFilter.prototype.filter = function(search_text) {
@@ -605,7 +605,7 @@
var occurrences;
occurrences = fuzzaldrinPlus.match(text, term);
return text.split('').map(function(character, i) {
- if (indexOf.call(occurrences, i) >= 0) {
+ if (indexOf.call(occurrences, i) !== -1) {
return "<b>" + character + "</b>";
} else {
return character;
@@ -748,7 +748,7 @@
return function(e) {
var $listItems, PREV_INDEX, currentKeyCode;
currentKeyCode = e.which;
- if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
+ if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
e.preventDefault();
e.stopImmediatePropagation();
PREV_INDEX = currentIndex;
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index 086dcb34571..ea5afbd9d29 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,4 +1,4 @@
-require('./stat_graph_contributors_graph');
-require('./stat_graph_contributors_util');
-require('./stat_graph_contributors');
-require('./stat_graph');
+import ContributorsStatGraph from './stat_graph_contributors';
+
+// export to global scope
+window.ContributorsStatGraph = ContributorsStatGraph;
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
deleted file mode 100644
index 75a53aae33c..00000000000
--- a/app/assets/javascripts/graphs/stat_graph.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */
-(function() {
- this.StatGraph = (function() {
- function StatGraph() {}
-
- StatGraph.log = {};
-
- StatGraph.get_log = function() {
- return this.log;
- };
-
- StatGraph.set_log = function(data) {
- return this.log = data;
- };
-
- return StatGraph;
- })();
-}).call(window);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index bbfb467ad50..c6be4c9e8fe 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -1,116 +1,111 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */
-/* global ContributorsGraph */
-/* global ContributorsAuthorGraph */
-/* global ContributorsMasterGraph */
-/* global ContributorsStatGraphUtil */
-/* global d3 */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
-window.d3 = require('d3');
+import d3 from 'd3';
+import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
+import ContributorsStatGraphUtil from './stat_graph_contributors_util';
-(function() {
- this.ContributorsStatGraph = (function() {
- function ContributorsStatGraph() {}
+export default (function() {
+ function ContributorsStatGraph() {}
- ContributorsStatGraph.prototype.init = function(log) {
- var author_commits, total_commits;
- this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
- this.set_current_field("commits");
- total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
- this.add_master_graph(total_commits);
- this.add_authors_graph(author_commits);
- return this.change_date_header();
- };
+ ContributorsStatGraph.prototype.init = function(log) {
+ var author_commits, total_commits;
+ this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
+ this.set_current_field("commits");
+ total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
+ this.add_master_graph(total_commits);
+ this.add_authors_graph(author_commits);
+ return this.change_date_header();
+ };
- ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
- this.master_graph = new ContributorsMasterGraph(total_data);
- return this.master_graph.draw();
- };
+ ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
+ this.master_graph = new ContributorsMasterGraph(total_data);
+ return this.master_graph.draw();
+ };
- ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
- var limited_author_data;
- this.authors = [];
- limited_author_data = author_data.slice(0, 100);
- return _.each(limited_author_data, (function(_this) {
- return function(d) {
- var author_graph, author_header;
- author_header = _this.create_author_header(d);
- $(".contributors-list").append(author_header);
- _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
- return author_graph.draw();
- };
- })(this));
- };
+ ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
+ var limited_author_data;
+ this.authors = [];
+ limited_author_data = author_data.slice(0, 100);
+ return _.each(limited_author_data, (function(_this) {
+ return function(d) {
+ var author_graph, author_header;
+ author_header = _this.create_author_header(d);
+ $(".contributors-list").append(author_header);
+ _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
+ return author_graph.draw();
+ };
+ })(this));
+ };
- ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
- var commits;
- commits = $('<span/>', {
- "class": 'graph-author-commits-count'
- });
- commits.text(author.commits + " commits");
- return $('<span/>').append(commits);
- };
+ ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
+ var commits;
+ commits = $('<span/>', {
+ "class": 'graph-author-commits-count'
+ });
+ commits.text(author.commits + " commits");
+ return $('<span/>').append(commits);
+ };
- ContributorsStatGraph.prototype.create_author_header = function(author) {
- var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
- list_item = $('<li/>', {
- "class": 'person',
- style: 'display: block;'
- });
- author_name = $('<h4>' + author.author_name + '</h4>');
- author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
- author_commit_info_span = $('<span/>', {
- "class": 'commits'
- });
- author_commit_info = this.format_author_commit_info(author);
- author_commit_info_span.html(author_commit_info);
- list_item.append(author_name);
- list_item.append(author_email);
- list_item.append(author_commit_info_span);
- return list_item;
- };
+ ContributorsStatGraph.prototype.create_author_header = function(author) {
+ var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
+ list_item = $('<li/>', {
+ "class": 'person',
+ style: 'display: block;'
+ });
+ author_name = $('<h4>' + author.author_name + '</h4>');
+ author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
+ author_commit_info_span = $('<span/>', {
+ "class": 'commits'
+ });
+ author_commit_info = this.format_author_commit_info(author);
+ author_commit_info_span.html(author_commit_info);
+ list_item.append(author_name);
+ list_item.append(author_email);
+ list_item.append(author_commit_info_span);
+ return list_item;
+ };
- ContributorsStatGraph.prototype.redraw_master = function() {
- var total_data;
- total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- this.master_graph.set_data(total_data);
- return this.master_graph.redraw();
- };
+ ContributorsStatGraph.prototype.redraw_master = function() {
+ var total_data;
+ total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ this.master_graph.set_data(total_data);
+ return this.master_graph.redraw();
+ };
- ContributorsStatGraph.prototype.redraw_authors = function() {
- var author_commits, x_domain;
- $("ol").html("");
- x_domain = ContributorsGraph.prototype.x_domain;
- author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
- return _.each(author_commits, (function(_this) {
- return function(d) {
- _this.redraw_author_commit_info(d);
- $(_this.authors[d.author_name].list_item).appendTo("ol");
- _this.authors[d.author_name].set_data(d.dates);
- return _this.authors[d.author_name].redraw();
- };
- })(this));
- };
+ ContributorsStatGraph.prototype.redraw_authors = function() {
+ var author_commits, x_domain;
+ $("ol").html("");
+ x_domain = ContributorsGraph.prototype.x_domain;
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
+ return _.each(author_commits, (function(_this) {
+ return function(d) {
+ _this.redraw_author_commit_info(d);
+ $(_this.authors[d.author_name].list_item).appendTo("ol");
+ _this.authors[d.author_name].set_data(d.dates);
+ return _this.authors[d.author_name].redraw();
+ };
+ })(this));
+ };
- ContributorsStatGraph.prototype.set_current_field = function(field) {
- return this.field = field;
- };
+ ContributorsStatGraph.prototype.set_current_field = function(field) {
+ return this.field = field;
+ };
- ContributorsStatGraph.prototype.change_date_header = function() {
- var print, print_date_format, x_domain;
- x_domain = ContributorsGraph.prototype.x_domain;
- print_date_format = d3.time.format("%B %e %Y");
- print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
- return $("#date_header").text(print);
- };
+ ContributorsStatGraph.prototype.change_date_header = function() {
+ var print, print_date_format, x_domain;
+ x_domain = ContributorsGraph.prototype.x_domain;
+ print_date_format = d3.time.format("%B %e %Y");
+ print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
+ return $("#date_header").text(print);
+ };
- ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
- var author_commit_info, author_list_item;
- author_list_item = $(this.authors[author.author_name].list_item);
- author_commit_info = this.format_author_commit_info(author);
- return author_list_item.find("span").html(author_commit_info);
- };
+ ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
+ var author_commit_info, author_list_item;
+ author_list_item = $(this.authors[author.author_name].list_item);
+ author_commit_info = this.format_author_commit_info(author);
+ return author_list_item.find("span").html(author_commit_info);
+ };
- return ContributorsStatGraph;
- })();
-}).call(window);
+ return ContributorsStatGraph;
+})();
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index 228771da4ee..521bc77db66 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -1,276 +1,272 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return */
-/* global d3 */
-/* global ContributorsGraph */
-
-window.d3 = require('d3');
-
-(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
- this.ContributorsGraph = (function() {
- function ContributorsGraph() {}
-
- ContributorsGraph.prototype.MARGIN = {
- top: 20,
- right: 20,
- bottom: 30,
- left: 50
- };
-
- ContributorsGraph.prototype.x_domain = null;
-
- ContributorsGraph.prototype.y_domain = null;
-
- ContributorsGraph.prototype.dates = [];
-
- ContributorsGraph.set_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = data;
- };
-
- ContributorsGraph.set_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
- };
-
- ContributorsGraph.init_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
- return d.date;
- });
- };
-
- ContributorsGraph.init_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
- };
-
- ContributorsGraph.init_domain = function(data) {
- ContributorsGraph.init_x_domain(data);
- return ContributorsGraph.init_y_domain(data);
- };
-
- ContributorsGraph.set_dates = function(data) {
- return ContributorsGraph.prototype.dates = data;
- };
-
- ContributorsGraph.prototype.set_x_domain = function() {
- return this.x.domain(this.x_domain);
- };
-
- ContributorsGraph.prototype.set_y_domain = function() {
- return this.y.domain(this.y_domain);
- };
-
- ContributorsGraph.prototype.set_domain = function() {
- this.set_x_domain();
- return this.set_y_domain();
- };
-
- ContributorsGraph.prototype.create_scale = function(width, height) {
- this.x = d3.time.scale().range([0, width]).clamp(true);
- return this.y = d3.scale.linear().range([height, 0]).nice();
- };
-
- ContributorsGraph.prototype.draw_x_axis = function() {
- return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
- };
-
- ContributorsGraph.prototype.draw_y_axis = function() {
- return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
- };
-
- ContributorsGraph.prototype.set_data = function(data) {
- return this.data = data;
- };
-
- return ContributorsGraph;
- })();
-
- this.ContributorsMasterGraph = (function(superClass) {
- extend(ContributorsMasterGraph, superClass);
-
- function ContributorsMasterGraph(data1) {
- this.data = data1;
- this.update_content = bind(this.update_content, this);
- this.width = $('.content').width() - 70;
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.brush = null;
- this.x_max_domain = null;
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
+
+import d3 from 'd3';
+
+const bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+const hasProp = {}.hasOwnProperty;
+
+export const ContributorsGraph = (function() {
+ function ContributorsGraph() {}
+
+ ContributorsGraph.prototype.MARGIN = {
+ top: 20,
+ right: 20,
+ bottom: 30,
+ left: 50
+ };
+
+ ContributorsGraph.prototype.x_domain = null;
+
+ ContributorsGraph.prototype.y_domain = null;
+
+ ContributorsGraph.prototype.dates = [];
+
+ ContributorsGraph.set_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = data;
+ };
+
+ ContributorsGraph.set_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ return d.commits = d.commits || d.additions || d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
+ return d.date;
+ });
+ };
+
+ ContributorsGraph.init_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ return d.commits = d.commits || d.additions || d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_domain = function(data) {
+ ContributorsGraph.init_x_domain(data);
+ return ContributorsGraph.init_y_domain(data);
+ };
+
+ ContributorsGraph.set_dates = function(data) {
+ return ContributorsGraph.prototype.dates = data;
+ };
+
+ ContributorsGraph.prototype.set_x_domain = function() {
+ return this.x.domain(this.x_domain);
+ };
+
+ ContributorsGraph.prototype.set_y_domain = function() {
+ return this.y.domain(this.y_domain);
+ };
+
+ ContributorsGraph.prototype.set_domain = function() {
+ this.set_x_domain();
+ return this.set_y_domain();
+ };
+
+ ContributorsGraph.prototype.create_scale = function(width, height) {
+ this.x = d3.time.scale().range([0, width]).clamp(true);
+ return this.y = d3.scale.linear().range([height, 0]).nice();
+ };
+
+ ContributorsGraph.prototype.draw_x_axis = function() {
+ return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
+ };
+
+ ContributorsGraph.prototype.draw_y_axis = function() {
+ return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
+ };
+
+ ContributorsGraph.prototype.set_data = function(data) {
+ return this.data = data;
+ };
+
+ return ContributorsGraph;
+})();
+
+export const ContributorsMasterGraph = (function(superClass) {
+ extend(ContributorsMasterGraph, superClass);
+
+ function ContributorsMasterGraph(data1) {
+ this.data = data1;
+ this.update_content = bind(this.update_content, this);
+ this.width = $('.content').width() - 70;
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.brush = null;
+ this.x_max_domain = null;
+ }
+
+ ContributorsMasterGraph.prototype.process_dates = function(data) {
+ var dates;
+ dates = this.get_dates(data);
+ this.parse_dates(data);
+ return ContributorsGraph.set_dates(dates);
+ };
+
+ ContributorsMasterGraph.prototype.get_dates = function(data) {
+ return _.pluck(data, 'date');
+ };
+
+ ContributorsMasterGraph.prototype.parse_dates = function(data) {
+ var parseDate;
+ parseDate = d3.time.format("%Y-%m-%d").parse;
+ return data.forEach(function(d) {
+ return d.date = parseDate(d.date);
+ });
+ };
+
+ ContributorsMasterGraph.prototype.create_scale = function() {
+ return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsMasterGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsMasterGraph.prototype.create_svg = function() {
+ return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsMasterGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
+ return x(d.date);
+ }).y0(this.height).y1(function(d) {
+ d.commits = d.commits || d.additions || d.deletions;
+ return y(d.commits);
+ }).interpolate("basis");
+ };
+
+ ContributorsMasterGraph.prototype.create_brush = function() {
+ return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
+ };
+
+ ContributorsMasterGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
+ };
+
+ ContributorsMasterGraph.prototype.add_brush = function() {
+ return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
+ };
+
+ ContributorsMasterGraph.prototype.update_content = function() {
+ ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
+ return $("#brush_change").trigger('change');
+ };
+
+ ContributorsMasterGraph.prototype.draw = function() {
+ this.process_dates(this.data);
+ this.create_scale();
+ this.create_axes();
+ ContributorsGraph.init_domain(this.data);
+ this.x_max_domain = this.x_domain;
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.create_brush();
+ this.draw_path(this.data);
+ this.draw_x_axis();
+ this.draw_y_axis();
+ return this.add_brush();
+ };
+
+ ContributorsMasterGraph.prototype.redraw = function() {
+ this.process_dates(this.data);
+ ContributorsGraph.set_y_domain(this.data);
+ this.set_y_domain();
+ this.svg.select("path").datum(this.data);
+ this.svg.select("path").attr("d", this.area);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsMasterGraph;
+})(ContributorsGraph);
+
+export const ContributorsAuthorGraph = (function(superClass) {
+ extend(ContributorsAuthorGraph, superClass);
+
+ function ContributorsAuthorGraph(data1) {
+ this.data = data1;
+ // Don't split graph size in half for mobile devices.
+ if ($(window).width() < 768) {
+ this.width = $('.content').width() - 80;
+ } else {
+ this.width = ($('.content').width() / 2) - 100;
}
-
- ContributorsMasterGraph.prototype.process_dates = function(data) {
- var dates;
- dates = this.get_dates(data);
- this.parse_dates(data);
- return ContributorsGraph.set_dates(dates);
- };
-
- ContributorsMasterGraph.prototype.get_dates = function(data) {
- return _.pluck(data, 'date');
- };
-
- ContributorsMasterGraph.prototype.parse_dates = function(data) {
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.list_item = null;
+ }
+
+ ContributorsAuthorGraph.prototype.create_scale = function() {
+ return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsAuthorGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsAuthorGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
var parseDate;
parseDate = d3.time.format("%Y-%m-%d").parse;
- return data.forEach(function(d) {
- return d.date = parseDate(d.date);
- });
- };
-
- ContributorsMasterGraph.prototype.create_scale = function() {
- return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsMasterGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
- return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
- };
-
- ContributorsMasterGraph.prototype.create_svg = function() {
- return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
- };
-
- ContributorsMasterGraph.prototype.create_area = function(x, y) {
- return this.area = d3.svg.area().x(function(d) {
- return x(d.date);
- }).y0(this.height).y1(function(d) {
- d.commits = d.commits || d.additions || d.deletions;
- return y(d.commits);
- }).interpolate("basis");
- };
-
- ContributorsMasterGraph.prototype.create_brush = function() {
- return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
- };
-
- ContributorsMasterGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
- };
-
- ContributorsMasterGraph.prototype.add_brush = function() {
- return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
- };
-
- ContributorsMasterGraph.prototype.update_content = function() {
- ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
- return $("#brush_change").trigger('change');
- };
-
- ContributorsMasterGraph.prototype.draw = function() {
- this.process_dates(this.data);
- this.create_scale();
- this.create_axes();
- ContributorsGraph.init_domain(this.data);
- this.x_max_domain = this.x_domain;
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.create_brush();
- this.draw_path(this.data);
- this.draw_x_axis();
- this.draw_y_axis();
- return this.add_brush();
- };
-
- ContributorsMasterGraph.prototype.redraw = function() {
- this.process_dates(this.data);
- ContributorsGraph.set_y_domain(this.data);
- this.set_y_domain();
- this.svg.select("path").datum(this.data);
- this.svg.select("path").attr("d", this.area);
- return this.svg.select(".y.axis").call(this.y_axis);
- };
-
- return ContributorsMasterGraph;
- })(ContributorsGraph);
-
- this.ContributorsAuthorGraph = (function(superClass) {
- extend(ContributorsAuthorGraph, superClass);
-
- function ContributorsAuthorGraph(data1) {
- this.data = data1;
- // Don't split graph size in half for mobile devices.
- if ($(window).width() < 768) {
- this.width = $('.content').width() - 80;
- } else {
- this.width = ($('.content').width() / 2) - 100;
- }
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.list_item = null;
- }
-
- ContributorsAuthorGraph.prototype.create_scale = function() {
- return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsAuthorGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
- return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
- };
-
- ContributorsAuthorGraph.prototype.create_area = function(x, y) {
- return this.area = d3.svg.area().x(function(d) {
- var parseDate;
- parseDate = d3.time.format("%Y-%m-%d").parse;
- return x(parseDate(d));
- }).y0(this.height).y1((function(_this) {
- return function(d) {
- if (_this.data[d] != null) {
- return y(_this.data[d]);
- } else {
- return y(0);
- }
- };
- })(this)).interpolate("basis");
- };
-
- ContributorsAuthorGraph.prototype.create_svg = function() {
- this.list_item = d3.selectAll(".person")[0].pop();
- return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
- };
-
- ContributorsAuthorGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
- };
-
- ContributorsAuthorGraph.prototype.draw = function() {
- this.create_scale();
- this.create_axes();
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.draw_path(this.dates);
- this.draw_x_axis();
- return this.draw_y_axis();
- };
-
- ContributorsAuthorGraph.prototype.redraw = function() {
- this.set_domain();
- this.svg.select("path").datum(this.dates);
- this.svg.select("path").attr("d", this.area);
- this.svg.select(".x.axis").call(this.x_axis);
- return this.svg.select(".y.axis").call(this.y_axis);
- };
-
- return ContributorsAuthorGraph;
- })(ContributorsGraph);
-}).call(window);
+ return x(parseDate(d));
+ }).y0(this.height).y1((function(_this) {
+ return function(d) {
+ if (_this.data[d] != null) {
+ return y(_this.data[d]);
+ } else {
+ return y(0);
+ }
+ };
+ })(this)).interpolate("basis");
+ };
+
+ ContributorsAuthorGraph.prototype.create_svg = function() {
+ this.list_item = d3.selectAll(".person")[0].pop();
+ return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsAuthorGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
+ };
+
+ ContributorsAuthorGraph.prototype.draw = function() {
+ this.create_scale();
+ this.create_axes();
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.draw_path(this.dates);
+ this.draw_x_axis();
+ return this.draw_y_axis();
+ };
+
+ ContributorsAuthorGraph.prototype.redraw = function() {
+ this.set_domain();
+ this.svg.select("path").datum(this.dates);
+ this.svg.select("path").attr("d", this.area);
+ this.svg.select(".x.axis").call(this.x_axis);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsAuthorGraph;
+})(ContributorsGraph);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index 7954c583598..c583757f3f2 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -1,138 +1,137 @@
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
-(function() {
- window.ContributorsStatGraphUtil = {
- parse_log: function(log) {
- var by_author, by_email, data, entry, i, len, total, normalized_email;
- total = {};
- by_author = {};
- by_email = {};
- for (i = 0, len = log.length; i < len; i += 1) {
- entry = log[i];
- if (total[entry.date] == null) {
- this.add_date(entry.date, total);
- }
- normalized_email = entry.author_email.toLowerCase();
- data = by_author[entry.author_name] || by_email[normalized_email];
- if (data == null) {
- data = this.add_author(entry, by_author, by_email);
- }
- if (!data[entry.date]) {
- this.add_date(entry.date, data);
- }
- this.store_data(entry, total[entry.date], data[entry.date]);
- }
- total = _.toArray(total);
- by_author = _.toArray(by_author);
- return {
- total: total,
- by_author: by_author
- };
- },
- add_date: function(date, collection) {
- collection[date] = {};
- return collection[date].date = date;
- },
- add_author: function(author, by_author, by_email) {
- var data, normalized_email;
- data = {};
- data.author_name = author.author_name;
- data.author_email = author.author_email;
- normalized_email = author.author_email.toLowerCase();
- by_author[author.author_name] = data;
- by_email[normalized_email] = data;
- return data;
- },
- store_data: function(entry, total, by_author) {
- this.store_commits(total, by_author);
- this.store_additions(entry, total, by_author);
- return this.store_deletions(entry, total, by_author);
- },
- store_commits: function(total, by_author) {
- this.add(total, "commits", 1);
- return this.add(by_author, "commits", 1);
- },
- add: function(collection, field, value) {
- if (collection[field] == null) {
- collection[field] = 0;
- }
- return collection[field] += value;
- },
- store_additions: function(entry, total, by_author) {
- if (entry.additions == null) {
- entry.additions = 0;
+
+export default {
+ parse_log: function(log) {
+ var by_author, by_email, data, entry, i, len, total, normalized_email;
+ total = {};
+ by_author = {};
+ by_email = {};
+ for (i = 0, len = log.length; i < len; i += 1) {
+ entry = log[i];
+ if (total[entry.date] == null) {
+ this.add_date(entry.date, total);
}
- this.add(total, "additions", entry.additions);
- return this.add(by_author, "additions", entry.additions);
- },
- store_deletions: function(entry, total, by_author) {
- if (entry.deletions == null) {
- entry.deletions = 0;
+ normalized_email = entry.author_email.toLowerCase();
+ data = by_author[entry.author_name] || by_email[normalized_email];
+ if (data == null) {
+ data = this.add_author(entry, by_author, by_email);
}
- this.add(total, "deletions", entry.deletions);
- return this.add(by_author, "deletions", entry.deletions);
- },
- get_total_data: function(parsed_log, field) {
- var log, total_data;
- log = parsed_log.total;
- total_data = this.pick_field(log, field);
- return _.sortBy(total_data, function(d) {
- return d.date;
- });
- },
- pick_field: function(log, field) {
- var total_data;
- total_data = [];
- _.each(log, function(d) {
- return total_data.push(_.pick(d, [field, 'date']));
- });
- return total_data;
- },
- get_author_data: function(parsed_log, field, date_range) {
- var author_data, log;
- if (date_range == null) {
- date_range = null;
- }
- log = parsed_log.by_author;
- author_data = [];
- _.each(log, (function(_this) {
- return function(log_entry) {
- var parsed_log_entry;
- parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
- if (!_.isEmpty(parsed_log_entry.dates)) {
- return author_data.push(parsed_log_entry);
- }
- };
- })(this));
- return _.sortBy(author_data, function(d) {
- return d[field];
- }).reverse();
- },
- parse_log_entry: function(log_entry, field, date_range) {
- var parsed_entry;
- parsed_entry = {};
- parsed_entry.author_name = log_entry.author_name;
- parsed_entry.author_email = log_entry.author_email;
- parsed_entry.dates = {};
- parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
- _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
- return function(value, key) {
- if (_this.in_range(value.date, date_range)) {
- parsed_entry.dates[value.date] = value[field];
- parsed_entry.commits += value.commits;
- parsed_entry.additions += value.additions;
- return parsed_entry.deletions += value.deletions;
- }
- };
- })(this));
- return parsed_entry;
- },
- in_range: function(date, date_range) {
- var ref;
- if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
- return true;
- } else {
- return false;
+ if (!data[entry.date]) {
+ this.add_date(entry.date, data);
}
+ this.store_data(entry, total[entry.date], data[entry.date]);
+ }
+ total = _.toArray(total);
+ by_author = _.toArray(by_author);
+ return {
+ total: total,
+ by_author: by_author
+ };
+ },
+ add_date: function(date, collection) {
+ collection[date] = {};
+ return collection[date].date = date;
+ },
+ add_author: function(author, by_author, by_email) {
+ var data, normalized_email;
+ data = {};
+ data.author_name = author.author_name;
+ data.author_email = author.author_email;
+ normalized_email = author.author_email.toLowerCase();
+ by_author[author.author_name] = data;
+ by_email[normalized_email] = data;
+ return data;
+ },
+ store_data: function(entry, total, by_author) {
+ this.store_commits(total, by_author);
+ this.store_additions(entry, total, by_author);
+ return this.store_deletions(entry, total, by_author);
+ },
+ store_commits: function(total, by_author) {
+ this.add(total, "commits", 1);
+ return this.add(by_author, "commits", 1);
+ },
+ add: function(collection, field, value) {
+ if (collection[field] == null) {
+ collection[field] = 0;
+ }
+ return collection[field] += value;
+ },
+ store_additions: function(entry, total, by_author) {
+ if (entry.additions == null) {
+ entry.additions = 0;
+ }
+ this.add(total, "additions", entry.additions);
+ return this.add(by_author, "additions", entry.additions);
+ },
+ store_deletions: function(entry, total, by_author) {
+ if (entry.deletions == null) {
+ entry.deletions = 0;
+ }
+ this.add(total, "deletions", entry.deletions);
+ return this.add(by_author, "deletions", entry.deletions);
+ },
+ get_total_data: function(parsed_log, field) {
+ var log, total_data;
+ log = parsed_log.total;
+ total_data = this.pick_field(log, field);
+ return _.sortBy(total_data, function(d) {
+ return d.date;
+ });
+ },
+ pick_field: function(log, field) {
+ var total_data;
+ total_data = [];
+ _.each(log, function(d) {
+ return total_data.push(_.pick(d, [field, 'date']));
+ });
+ return total_data;
+ },
+ get_author_data: function(parsed_log, field, date_range) {
+ var author_data, log;
+ if (date_range == null) {
+ date_range = null;
+ }
+ log = parsed_log.by_author;
+ author_data = [];
+ _.each(log, (function(_this) {
+ return function(log_entry) {
+ var parsed_log_entry;
+ parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
+ if (!_.isEmpty(parsed_log_entry.dates)) {
+ return author_data.push(parsed_log_entry);
+ }
+ };
+ })(this));
+ return _.sortBy(author_data, function(d) {
+ return d[field];
+ }).reverse();
+ },
+ parse_log_entry: function(log_entry, field, date_range) {
+ var parsed_entry;
+ parsed_entry = {};
+ parsed_entry.author_name = log_entry.author_name;
+ parsed_entry.author_email = log_entry.author_email;
+ parsed_entry.dates = {};
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
+ _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
+ return function(value, key) {
+ if (_this.in_range(value.date, date_range)) {
+ parsed_entry.dates[value.date] = value[field];
+ parsed_entry.commits += value.commits;
+ parsed_entry.additions += value.additions;
+ return parsed_entry.deletions += value.deletions;
+ }
+ };
+ })(this));
+ return parsed_entry;
+ },
+ in_range: function(date, date_range) {
+ var ref;
+ if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
+ return true;
+ } else {
+ return false;
}
- };
-}).call(window);
+ }
+};
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
index 8df86f68218..3bfce32768a 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js.es6
@@ -116,7 +116,7 @@
formData = $.param(formData);
formAction = form.attr('action');
issuesUrl = formAction;
- issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
+ issuesUrl += "" + (formAction.indexOf('?') === -1 ? '?' : '&');
issuesUrl += formData;
return gl.utils.visitUrl(issuesUrl);
};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 45a1d90a9d9..0242350f718 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -296,5 +296,58 @@
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
+
+ /**
+ * Back Off exponential algorithm
+ * backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
+ *
+ * @param {Function<next, stop>} fn function to be called
+ * @param {Number} timeout
+ * @return {Promise<Any, Error>}
+ * @example
+ * ```
+ * backOff(function (next, stop) {
+ * // Let's perform this function repeatedly for 60s or for the timeout provided.
+ *
+ * ourFunction()
+ * .then(function (result) {
+ * // continue if result is not what we need
+ * next();
+ *
+ * // when result is what we need let's stop with the repetions and jump out of the cycle
+ * stop(result);
+ * })
+ * .catch(function (error) {
+ * // if there is an error, we need to stop this with an error.
+ * stop(error);
+ * })
+ * }, 60000)
+ * .then(function (result) {})
+ * .catch(function (error) {
+ * // deal with errors passed to stop()
+ * })
+ * ```
+ */
+ w.gl.utils.backOff = (fn, timeout = 60000) => {
+ const maxInterval = 32000;
+ let nextInterval = 2000;
+
+ const startTime = Date.now();
+
+ return new Promise((resolve, reject) => {
+ const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
+
+ const next = () => {
+ if (Date.now() - startTime < timeout) {
+ setTimeout(fn.bind(null, next, stop), nextInterval);
+ nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
+ } else {
+ reject(new Error('BACKOFF_TIMEOUT'));
+ }
+ };
+
+ fn(next, stop);
+ });
+ };
})(window);
}).call(window);
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
new file mode 100644
index 00000000000..bc109a69c20
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -0,0 +1,10 @@
+/**
+ * exports HTTP status codes
+ */
+
+const statusCodes = {
+ NO_CONTENT: 204,
+ OK: 200,
+};
+
+module.exports = statusCodes;
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 88f08bbaa34..00c6c050612 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -83,7 +83,7 @@ require('./smart_interval');
return function() {
var page;
page = $('body').data('page').split(':').last();
- if (allowedPages.indexOf(page) < 0) {
+ if (allowedPages.indexOf(page) === -1) {
return _this.clearEventListeners();
}
};
@@ -233,7 +233,7 @@ require('./smart_interval');
}
$('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
- if (indexOf.call(allowed_states, state) >= 0) {
+ if (indexOf.call(allowed_states, state) !== -1) {
$('.ci_widget.ci-' + state).show();
switch (state) {
case "failed":
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 7fbaeec7882..38c673e8907 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -78,7 +78,6 @@
} else {
$(element).find('.assignee-icon').empty();
}
- return $(element).effect('highlight');
};
function Milestone() {
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 8df1c8e7f94..51fa5c828b3 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -39,7 +39,7 @@
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) {
- milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+ milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
}
@@ -181,8 +181,7 @@
$selectbox.hide();
$value.css('display', '');
if (data.milestone != null) {
- data.milestone.namespace = _this.currentProject.namespace;
- data.milestone.path = _this.currentProject.path;
+ data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index cb24f212c66..5828f460a23 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
@@ -20,15 +20,35 @@
};
NewBranchForm.prototype.init = function() {
- if (this.name.val().length > 0) {
+ if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
};
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
- return this.ref.autocomplete({
- source: availableRefs,
- minLength: 1
+ var $branchSelect = $('.js-branch-select');
+
+ $branchSelect.glDropdown({
+ data: availableRefs,
+ filterable: true,
+ filterByText: true,
+ remote: false,
+ fieldName: $branchSelect.data('field-name'),
+ selectable: true,
+ isSelectable: function(branch, $el) {
+ return !$el.hasClass('is-active');
+ },
+ text: function(branch) {
+ return branch;
+ },
+ id: function(branch) {
+ return branch;
+ },
+ toggleLabel: function(branch) {
+ if (branch) {
+ return branch;
+ }
+ }
});
};
@@ -61,7 +81,7 @@
var errorMessage, errors, formatter, unique, validator;
this.branchNameError.empty();
unique = function(values, value) {
- if (indexOf.call(values, value) < 0) {
+ if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6
index 81374296522..4ccea0624ee 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js.es6
@@ -84,13 +84,14 @@
}
$(function() {
- $(document).on('focusout.ssh_key', '#key_key', function() {
+ $(document).on('input.ssh_key', '#key_key', function() {
const $title = $('#key_title');
const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
- if (comment && comment.length > 1 && $title.val() === '') {
+
+ // Extract the SSH Key title from its comment
+ if (comment && comment.length > 1) {
return $title.val(comment[1]).change();
}
- // Extract the SSH Key title from its comment
});
if (global.utils.getPagePath() === 'profiles') {
return new Profile();
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
index d7f3c9fd37e..15d32825583 100644
--- a/app/assets/javascripts/profile/profile_bundle.js
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -1,3 +1,2 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
+require('./gl_crop');
+require('./profile');
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 7c03c8b72d4..db7ceaa2421 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -116,7 +116,7 @@
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form');
var action = $form.attr('action');
- var divider = action.indexOf('?') < 0 ? '?' : '&';
+ var divider = action.indexOf('?') === -1 ? '?' : '&';
gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
}
}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
index 149e511451e..6ef59e94384 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
@@ -36,6 +36,9 @@
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
+ this.$allowedToMergeDropdown.disable();
+ this.$allowedToPushDropdown.disable();
+
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
@@ -53,13 +56,13 @@
}]
}
},
- success: () => {
- this.$wrap.effect('highlight');
- },
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
+ }).always(() => {
+ this.$allowedToMergeDropdown.enable();
+ this.$allowedToPushDropdown.enable();
});
}
};
diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
index ffb66caf5f4..849c1e31623 100644
--- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js
+++ b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
@@ -1,3 +1,5 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/));
+require('./protected_branch_access_dropdown');
+require('./protected_branch_create');
+require('./protected_branch_dropdown');
+require('./protected_branch_edit');
+require('./protected_branch_edit_list');
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index 89822246bb8..a98403f4cf2 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -1,10 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
-
(function() {
$(function() {
var editor = ace.edit("editor");
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index dfe24d1fb33..b1402c0a880 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -1,3 +1,4 @@
+/* global Flash */
require('vendor/task_list');
class TaskList {
@@ -6,6 +7,16 @@ class TaskList {
this.dataType = options.dataType;
this.fieldName = options.fieldName;
this.onSuccess = options.onSuccess || (() => {});
+ this.onError = function showFlash(response) {
+ let errorMessages = '';
+
+ if (response.responseJSON) {
+ errorMessages = response.responseJSON.errors.join(' ');
+ }
+
+ return new Flash(errorMessages || 'Update failed', 'alert');
+ };
+
this.init();
}
@@ -32,6 +43,7 @@ class TaskList {
url: $target.data('update-url') || $('form.js-issuable-update').attr('action'),
data: patchData,
success: this.onSuccess,
+ error: this.onError,
});
}
}
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
new file mode 100644
index 00000000000..74b869502a4
--- /dev/null
+++ b/app/assets/javascripts/user_callout.js
@@ -0,0 +1,58 @@
+/* global Cookies */
+
+const userCalloutElementName = '.user-callout';
+const closeButton = '.close-user-callout';
+const userCalloutBtn = '.user-callout-btn';
+const userCalloutSvgAttrName = 'callout-svg';
+
+const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
+
+const USER_CALLOUT_TEMPLATE = `
+ <div class="bordered-box landing content-block">
+ <button class="btn btn-default close close-user-callout" type="button">
+ <i class="fa fa-times dismiss-icon"></i>
+ </button>
+ <div class="row">
+ <div class="col-sm-3 col-xs-12 svg-container">
+ </div>
+ <div class="col-sm-8 col-xs-12 inner-content">
+ <h4>
+ Customize your experience
+ </h4>
+ <p>
+ Change syntax themes, default project pages, and more in preferences.
+ </p>
+ <a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
+ </div>
+ </div>
+</div>`;
+
+class UserCallout {
+ constructor() {
+ this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
+ this.userCalloutBody = $(userCalloutElementName);
+ this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
+ $(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
+ this.init();
+ }
+
+ init() {
+ const $template = $(USER_CALLOUT_TEMPLATE);
+ if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
+ $template.find('.svg-container').append(this.userCalloutSvg);
+ this.userCalloutBody.append($template);
+ $template.find(closeButton).on('click', e => this.dismissCallout(e));
+ $template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
+ }
+ }
+
+ dismissCallout(e) {
+ Cookies.set(USER_CALLOUT_COOKIE, 'true');
+ const $currentTarget = $(e.currentTarget);
+ if ($currentTarget.hasClass('close-user-callout')) {
+ this.userCalloutBody.empty();
+ }
+ }
+}
+
+module.exports = UserCallout;
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
index 4cad60a59b1..580e2d84be5 100644
--- a/app/assets/javascripts/users/users_bundle.js
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -1,3 +1 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
+require('./calendar');
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
index 54e8f977a47..e20085d1fd2 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -1,5 +1,5 @@
/* global Vue, Flash, gl */
-/* eslint-disable no-param-reassign */
+/* eslint-disable no-param-reassign, no-alert */
((gl) => {
gl.VuePipelineActions = Vue.extend({
@@ -16,21 +16,33 @@
download(name) {
return `Download ${name} artifacts`;
},
+
+ /**
+ * Shows a dialog when the user clicks in the cancel button.
+ * We need to prevent the default behavior and stop propagation because the
+ * link relies on UJS.
+ *
+ * @param {Event} event
+ */
+ confirmAction(event) {
+ if (!confirm('Are you sure you want to cancel this pipeline?')) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
},
template: `
<td class="pipeline-actions hidden-xs">
- <div class="controls pull-right">
- <div class="btn-group inline">
- <div class="btn-group">
+ <div class="pull-right">
+ <div class="btn-group">
+ <div class="btn-group" v-if="actions">
<button
- v-if='actions'
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
data-toggle="dropdown"
title="Manual job"
data-placement="top"
- aria-label="Manual job"
- >
- <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+ aria-label="Manual job">
+ <span v-html="svgs.iconPlay" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
@@ -38,23 +50,21 @@
<a
rel="nofollow"
data-method="post"
- :href='action.path'
- >
- <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+ :href="action.path">
+ <span v-html="svgs.iconPlay" aria-hidden="true"></span>
<span>{{action.name}}</span>
</a>
</li>
</ul>
</div>
- <div class="btn-group">
+
+ <div class="btn-group" v-if="artifacts">
<button
- v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
data-toggle="dropdown"
- aria-label="Artifacts"
- >
+ aria-label="Artifacts">
<i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
@@ -62,41 +72,39 @@
<li v-for='artifact in pipeline.details.artifacts'>
<a
rel="nofollow"
- download
- :href='artifact.path'
- >
+ :href="artifact.path">
<i class="fa fa-download" aria-hidden="true"></i>
<span>{{download(artifact.name)}}</span>
</a>
</li>
</ul>
</div>
- </div>
- <div class="cancel-retry-btns inline">
- <a
- v-if='pipeline.flags.retryable'
- class="btn has-tooltip"
- title="Retry"
- rel="nofollow"
- data-method="post"
- data-placement="top"
- data-toggle="dropdown"
- :href='pipeline.retry_path'
- aria-label="Retry">
- <i class="fa fa-repeat" aria-hidden="true"></i>
- </a>
- <a
- v-if='pipeline.flags.cancelable'
- class="btn btn-remove has-tooltip"
- title="Cancel"
- rel="nofollow"
- data-method="post"
- data-placement="top"
- data-toggle="dropdown"
- :href='pipeline.cancel_path'
- aria-label="Cancel">
- <i class="fa fa-remove" aria-hidden="true"></i>
- </a>
+ <div class="btn-group" v-if="pipeline.flags.retryable">
+ <a
+ class="btn btn-default btn-retry has-tooltip"
+ title="Retry"
+ rel="nofollow"
+ data-method="post"
+ data-placement="top"
+ data-toggle="dropdown"
+ :href='pipeline.retry_path'
+ aria-label="Retry">
+ <i class="fa fa-repeat" aria-hidden="true"></i>
+ </a>
+ </div>
+ <div class="btn-group" v-if="pipeline.flags.cancelable">
+ <a
+ class="btn btn-remove has-tooltip"
+ title="Cancel"
+ rel="nofollow"
+ data-method="post"
+ data-placement="top"
+ data-toggle="dropdown"
+ :href='pipeline.cancel_path'
+ aria-label="Cancel">
+ <i class="fa fa-remove" aria-hidden="true"></i>
+ </a>
+ </div>
</div>
</div>
</td>
diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
index 3598da11573..6048fa691dc 100644
--- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
@@ -54,7 +54,7 @@ require('../lib/utils/datetime_utility');
},
},
template: `
- <td>
+ <td class="pipelines-time-ago">
<p class="duration" v-if='duration'>
<span v-html='svgs.iconTimer'></span>
{{duration}}
@@ -65,8 +65,7 @@ require('../lib/utils/datetime_utility');
data-toggle="tooltip"
data-placement="top"
data-container="body"
- :data-original-title='localTimeFinished'
- >
+ :data-original-title='localTimeFinished'>
{{timeStopped.words}}
</time>
</p>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 1dcd1f8a6fc..83a8eeaafde 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
*= require_self
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 0f9213b98e3..9a4129cdc8d 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -229,7 +229,7 @@
.controls {
float: right;
margin-top: 8px;
- padding-bottom: 7px;
+ padding-bottom: 8px;
border-bottom: 1px solid $border-color;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index e3da467a27c..d2be8dc7a39 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -26,6 +26,11 @@
.filtered-search-container {
display: -webkit-flex;
display: flex;
+
+ @media (max-width: $screen-xs-min) {
+ -webkit-flex-direction: column;
+ flex-direction: column;
+ }
}
.filtered-search-input-container {
@@ -34,6 +39,20 @@
position: relative;
width: 100%;
+ @media (max-width: $screen-xs-min) {
+ -webkit-flex: 1 1 100%;
+ flex: 1 1 100%;
+ margin-bottom: 10px;
+
+ .dropdown-menu {
+ width: auto;
+ left: 0;
+ right: 0;
+ max-width: none;
+ min-width: 100%;
+ }
+ }
+
.form-control {
padding-left: 25px;
padding-right: 25px;
@@ -79,6 +98,31 @@
overflow: auto;
}
+@media (max-width: $screen-xs-min) {
+ .issues-details-filters {
+ padding: 0 0 10px;
+ background-color: $white-light;
+ border-top: 0;
+ }
+
+ .filter-dropdown-container {
+ .dropdown-toggle,
+ .dropdown {
+ width: 100%;
+ }
+
+ .dropdown {
+ margin-left: 0;
+ }
+
+ .fa-chevron-down {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ }
+ }
+}
+
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
@@ -148,4 +192,4 @@
.filter-dropdown-loading {
padding: 8px 16px;
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index d335fedefe2..300ba4f2de6 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -2,17 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
- &.ui-autocomplete {
- border-color: $jq-ui-border;
- padding: 0;
- margin-top: 2px;
- z-index: 1001;
-
- .ui-menu-item a {
- padding: 4px 10px;
- }
- }
-
.ui-state-default {
border: 1px solid $white-light;
background: $white-light;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 6f2e746d4b0..9c76e58dfc8 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -20,6 +20,7 @@ $dark-highlight-bg: #ffe792;
$dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
+$dark-over-bg: #9f9ab5;
$dark-c: #969896;
$dark-err: #c66;
$dark-k: #b294bb;
@@ -139,6 +140,18 @@ $dark-il: #de935f;
}
}
+ .diff-line-num {
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $dark-over-bg;
+ border-color: darken($dark-over-bg, 5%);
+
+ a {
+ color: darken($dark-over-bg, 15%);
+ }
+ }
+ }
+
.line_content.match {
@include dark-diff-match-line;
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 2144a5f7466..6488a099c74 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -13,6 +13,7 @@ $monokai-line-empty-bg: #49483e;
$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
+$monokai-over-bg: #9f9ab5;
$monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15);
@@ -139,6 +140,18 @@ $monokai-gi: #a6e22e;
}
}
+ .diff-line-num {
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $monokai-over-bg;
+ border-color: darken($monokai-over-bg, 5%);
+
+ a {
+ color: darken($monokai-over-bg, 15%);
+ }
+ }
+ }
+
.line_content.match {
@include dark-diff-match-line;
}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 2cb1d18f12f..00079cc2b5b 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -17,6 +17,7 @@ $solarized-dark-line-color-new: #5a766c;
$solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
+$solarized-dark-over-bg: #9f9ab5;
$solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1;
@@ -143,6 +144,18 @@ $solarized-dark-il: #2aa198;
}
}
+ .diff-line-num {
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $solarized-dark-over-bg;
+ border-color: darken($solarized-dark-over-bg, 5%);
+
+ a {
+ color: darken($solarized-dark-over-bg, 15%);
+ }
+ }
+ }
+
.line_content.match {
@include dark-diff-match-line;
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index b72c4326730..2e3b68f1a58 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -18,6 +18,7 @@ $solarized-light-line-color-new: #a1a080;
$solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
+$solarized-light-over-bg: #ded7fc;
$solarized-light-c: #93a1a1;
$solarized-light-err: #586e75;
$solarized-light-g: #586e75;
@@ -150,6 +151,18 @@ $solarized-light-il: #2aa198;
}
}
+ .diff-line-num {
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $solarized-light-over-bg;
+ border-color: darken($solarized-light-over-bg, 5%);
+
+ a {
+ color: darken($solarized-light-over-bg, 15%);
+ }
+ }
+ }
+
.line_content.match {
@include matchLine;
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 398fbfd3b18..0eca30e570f 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -7,6 +7,7 @@ $white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
+$white-over-bg: #ded7fc;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
@@ -123,6 +124,16 @@ $white-gc-bg: #eaf2f5;
}
}
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $white-over-bg;
+ border-color: darken($white-over-bg, 5%);
+
+ a {
+ color: darken($white-over-bg, 15%);
+ }
+ }
+
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 92d7772da57..339cdcde480 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -89,6 +89,10 @@
.diff-line-num {
width: 50px;
+
+ a {
+ transition: none;
+ }
}
.line_holder td {
@@ -109,10 +113,6 @@
td.line_content.parallel {
width: 46%;
}
-
- .add-diff-note {
- margin-left: -65px;
- }
}
.old_line,
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index aa130a1abb0..00f5f2645b3 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -452,36 +452,37 @@ ul.notes {
* Line note button on the side of diffs
*/
-.diff-file tr.line_holder {
- @mixin show-add-diff-note {
- display: inline-block;
- }
+.add-diff-note {
+ display: none;
+ margin-top: -2px;
+ border-radius: 50%;
+ background: $white-light;
+ padding: 1px 5px;
+ font-size: 12px;
+ color: $gl-link-color;
+ margin-left: -55px;
+ position: absolute;
+ z-index: 10;
+ width: 23px;
+ height: 23px;
+ border: 1px solid $border-color;
+ transition: transform .1s ease-in-out;
- .add-diff-note {
- margin-top: -8px;
- border-radius: 40px;
- background: $white-light;
- padding: 4px;
- font-size: 16px;
- color: $gl-link-color;
- margin-left: -56px;
- position: absolute;
- z-index: 10;
- width: 32px;
- // "hide" it by default
- display: none;
+ &:hover {
+ background: $gl-info;
+ color: $white-light;
+ transform: scale(1.15);
+ }
- &:hover {
- background: $gl-info;
- color: $white-light;
- @include show-add-diff-note;
- }
+ &:active {
+ outline: 0;
}
+}
- // "show" the icon also if we just hover somewhere over the line
- &:hover > td {
+.diff-file {
+ .is-over {
.add-diff-note {
- @include show-add-diff-note;
+ display: inline-block;
}
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 3fe1eef307e..f4707f71208 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -13,21 +13,16 @@
white-space: nowrap;
}
- .commit-title {
- margin: 0;
- }
-
- .controls {
- white-space: nowrap;
+ .table-holder {
+ width: 100%;
+ overflow: auto;
}
- .btn {
- margin: 4px;
+ .commit-title {
+ margin: 0;
}
.table.ci-table {
- min-width: 1200px;
- table-layout: fixed;
.label {
margin-bottom: 3px;
@@ -37,16 +32,72 @@
color: $black;
}
- .pipeline-date,
- .pipeline-status {
- width: 10%;
+ .stage-cell {
+ min-width: 130px; // Guarantees we show at least 4 stages in line
+ width: 20%;
+ }
+
+ .pipelines-time-ago {
+ text-align: right;
}
- .pipeline-info,
- .pipeline-commit,
- .pipeline-stages,
.pipeline-actions {
- width: 20%;
+ padding-right: 0;
+ min-width: 170px; //Guarantees buttons don't break in several lines.
+
+ .btn-default {
+ color: $gl-text-color-secondary;
+ }
+
+ .btn.btn-retry:hover,
+ .btn.btn-retry:focus {
+ border-color: $gray-darkest;
+ background-color: $white-normal;
+ }
+
+ svg path {
+ fill: $gl-text-color-secondary;
+ }
+
+ .dropdown-menu {
+ max-height: 250px;
+ overflow-y: auto;
+ }
+
+ .dropdown-toggle,
+ .dropdown-menu {
+ color: $gl-text-color-secondary;
+
+ .fa {
+ color: $gl-text-color-secondary;
+ font-size: 14px;
+ }
+
+ svg,
+ .fa {
+ margin-right: 0;
+ }
+ }
+
+ .btn-group {
+ &.open {
+ .btn-default {
+ background-color: $white-normal;
+ border-color: $border-white-normal;
+ }
+ }
+
+ .btn {
+ .icon-play {
+ height: 13px;
+ width: 12px;
+ }
+ }
+ }
+
+ .tooltip {
+ white-space: nowrap;
+ }
}
}
}
@@ -61,27 +112,10 @@
}
}
-.content-list.pipelines .table-holder {
- min-height: 300px;
-}
-
-.pipeline-holder {
- width: 100%;
- overflow: auto;
-}
-
.table.ci-table {
- min-width: 900px;
-
- &.pipeline {
- min-width: 650px;
- }
-
- &.builds-page {
- tr {
- height: 71px;
- }
+ &.builds-page tr {
+ height: 71px;
}
tr {
@@ -94,12 +128,16 @@
padding: 10px 8px;
}
+ td.environments-actions {
+ padding-right: 0;
+ }
+
td.stage-cell {
padding: 10px 0;
}
.commit-link {
- padding: 9px 8px 10px;
+ padding: 9px 8px 10px 2px;
}
}
@@ -206,72 +244,8 @@
}
}
- .pipeline-actions {
- min-width: 140px;
-
- .btn {
- margin: 0;
- color: $gl-text-color-secondary;
- }
-
- .cancel-retry-btns {
- vertical-align: middle;
-
- .btn:not(:first-child) {
- margin-left: 8px;
- }
- }
-
- .dropdown-menu {
- max-height: 250px;
- overflow-y: auto;
- }
-
- .dropdown-toggle,
- .dropdown-menu {
- color: $gl-text-color-secondary;
-
- .fa {
- color: $gl-text-color-secondary;
- font-size: 14px;
- }
-
- svg,
- .fa {
- margin-right: 0;
- }
- }
-
- .btn-remove {
- color: $white-light;
- }
-
- .btn-group {
- &.open {
- .btn-default {
- background-color: $white-normal;
- border-color: $border-white-normal;
- }
- }
-
- .btn {
- .icon-play {
- height: 13px;
- width: 12px;
- }
- }
- }
-
- .tooltip {
- white-space: nowrap;
- }
- }
-
- .build-link {
-
- a {
- color: $gl-text-color;
- }
+ .build-link a {
+ color: $gl-text-color;
}
.btn-group.open .dropdown-toggle {
@@ -335,31 +309,8 @@
}
.tab-pane {
- &.pipelines {
- .ci-table {
- min-width: 900px;
- }
-
- .content-list.pipelines {
- overflow: auto;
- }
-
- .stage {
- max-width: 100px;
- width: 100px;
- }
-
- .pipeline-actions {
- min-width: initial;
- }
- }
-
- &.builds {
- .ci-table {
- tr {
- height: 71px;
- }
- }
+ &.builds .ci-table tr {
+ height: 71px;
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8031c4467a4..aad1a8986b0 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px;
}
}
+
+.user-callout {
+ margin: 24px auto 0;
+
+ .bordered-box {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ }
+
+ .landing {
+ margin-bottom: $gl-padding;
+
+ .close {
+ margin-right: 20px;
+ }
+
+ .dismiss-icon {
+ float: right;
+ cursor: pointer;
+ color: $cycle-analytics-dismiss-icon-color;
+ }
+
+ .svg-container {
+ text-align: center;
+
+ svg {
+ width: 136px;
+ height: 136px;
+ }
+ }
+ }
+
+ @media(max-width: $screen-xs-max) {
+ .inner-content {
+ padding-left: 30px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 67110813abb..07b93430442 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -638,14 +638,6 @@ pre.light-well {
margin: 0;
}
-.activity-filter-block {
- .controls {
- padding-bottom: 7px;
- margin-top: 8px;
- border-bottom: 1px solid $border-color;
- }
-}
-
.commits-search-form {
.input-short {
min-width: 200px;
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 0821974aa93..3ccf2a9ce33 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -26,6 +26,23 @@ module IssuableActions
private
+ def render_conflict_response
+ respond_to do |format|
+ format.html do
+ @conflict = true
+ render :edit
+ end
+
+ format.json do
+ render json: {
+ errors: [
+ "Someone edited this #{issuable.human_class_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."
+ ]
+ }, status: 409
+ end
+ end
+ end
+
def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index e610ccaec96..2992568ae66 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -33,6 +33,7 @@ module ServiceParams
:issues_url,
:jira_issue_transition_id,
:merge_requests_events,
+ :mock_service_url,
:namespace,
:new_issue_url,
:notify,
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ca5e81100da..1151555b8fa 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -134,8 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
rescue ActiveRecord::StaleObjectError
- @conflict = true
- render :edit
+ render_conflict_response
end
def referenced_merge_requests
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d122c7fdcb2..53f30a24312 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -296,22 +296,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def update
@merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
- if @merge_request.valid?
- respond_to do |format|
- format.html do
- redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
- @merge_request.target_project, @merge_request])
- end
- format.json do
- render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
+ respond_to do |format|
+ format.html do
+ if @merge_request.valid?
+ redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
+ else
+ render :edit
end
end
- else
- render "edit"
+
+ format.json do
+ render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
+ end
end
rescue ActiveRecord::StaleObjectError
- @conflict = true
- render :edit
+ render_conflict_response
end
def remove_wip
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index ff937b5ebd2..5ac3e66bb1f 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -15,4 +15,11 @@ module BuildsHelper
log_state: @build.trace_with_state[:state].to_s
}
end
+
+ def build_failed_issue_options
+ {
+ title: "Build Failed ##{@build.id}",
+ description: namespace_project_build_url(@project.namespace, @project, @build)
+ }
+ end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 4c7c16d694c..195094730aa 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -34,7 +34,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol,
class: klass,
- href: (project.http_url_to_repo if append_link),
+ href: (project.http_url_to_repo(current_user) if append_link),
data: {
html: true,
placement: placement,
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 715072290c6..860a665ae26 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template',
selected: selected_template(issuable),
project_path: ref_project.path,
- namespace_path: ref_project.namespace.path
+ namespace_path: ref_project.namespace.full_path
}
}
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 735a355c25a..4befeacc135 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -241,7 +241,7 @@ module ProjectsHelper
when 'ssh'
project.ssh_url_to_repo
else
- project.http_url_to_repo
+ project.http_url_to_repo(current_user)
end
end
diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb
new file mode 100644
index 00000000000..a7fe5951b6e
--- /dev/null
+++ b/app/models/concerns/uniquify.rb
@@ -0,0 +1,30 @@
+class Uniquify
+ # Return a version of the given 'base' string that is unique
+ # by appending a counter to it. Uniqueness is determined by
+ # repeated calls to the passed block.
+ #
+ # If `base` is a function/proc, we expect that calling it with a
+ # candidate counter returns a string to test/return.
+ def string(base)
+ @base = base
+ @counter = nil
+
+ increment_counter! while yield(base_string)
+ base_string
+ end
+
+ private
+
+ def base_string
+ if @base.respond_to?(:call)
+ @base.call(@counter)
+ else
+ "#{@base}#{@counter}"
+ end
+ end
+
+ def increment_counter!
+ @counter ||= 0
+ @counter += 1
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 4b8eac9accf..d7ca8e3c599 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
- where(project_id: projects).recent
+ where(project_id: projects.pluck(:id)).recent
end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bd0336c984a..3137dd32f93 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -98,14 +98,8 @@ class Namespace < ActiveRecord::Base
# Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
- counter = 0
- base = path
- while Namespace.find_by_path_or_name(path)
- counter += 1
- path = "#{base}#{counter}"
- end
-
- path
+ uniquify = Uniquify.new
+ uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 814fd0c0f4f..e06fc30dc8a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -359,7 +359,7 @@ class Project < ActiveRecord::Base
end
def reference_pattern
- name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+ name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{
((?<namespace>#{name_pattern})\/)?
@@ -847,10 +847,6 @@ class Project < ActiveRecord::Base
gitlab_shell.url_to_repo(path_with_namespace)
end
- def namespace_dir
- namespace.try(:path) || ''
- end
-
def repo_exists?
@repo_exists ||= repository.exists?
rescue
@@ -873,8 +869,14 @@ class Project < ActiveRecord::Base
url_to_repo
end
- def http_url_to_repo
- "#{web_url}.git"
+ def http_url_to_repo(user = nil)
+ url = web_url
+
+ if user
+ url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" }
+ end
+
+ "#{url}.git"
end
# Check if current branch name is marked as protected in the system
@@ -899,8 +901,8 @@ class Project < ActiveRecord::Base
def rename_repo
path_was = previous_changes['path'].first
- old_path_with_namespace = File.join(namespace_dir, path_was)
- new_path_with_namespace = File.join(namespace_dir, path)
+ old_path_with_namespace = File.join(namespace.full_path, path_was)
+ new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 4ebc5318da1..c13538e9fea 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -15,10 +15,10 @@ class MattermostService < ChatNotificationService
'This service sends notifications about projects events to Mattermost channels.<br />
To set up this service:
<ol>
- <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li>
- <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li>
- <li>Paste the webhook <strong>URL</strong> into the field bellow. </li>
- <li>Select events below to enable notifications. The channel and username are optional. </li>
+ <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation.</li>
+ <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event.</li>
+ <li>Paste the webhook <strong>URL</strong> into the field below.</li>
+ <li>Select events below to enable notifications. The <strong>Channel handle</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
@@ -28,14 +28,14 @@ class MattermostService < ChatNotificationService
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
- { type: 'text', name: 'username', placeholder: 'username' },
+ { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
- "town-square"
+ "Channel handle (e.g. town-square)"
end
end
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
new file mode 100644
index 00000000000..a8d581a1f67
--- /dev/null
+++ b/app/models/project_services/mock_ci_service.rb
@@ -0,0 +1,82 @@
+# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
+class MockCiService < CiService
+ ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
+
+ prop_accessor :mock_service_url
+ validates :mock_service_url, presence: true, url: true, if: :activated?
+
+ def title
+ 'MockCI'
+ end
+
+ def description
+ 'Mock an external CI'
+ end
+
+ def self.to_param
+ 'mock_ci'
+ end
+
+ def fields
+ [
+ { type: 'text',
+ name: 'mock_service_url',
+ placeholder: 'http://localhost:4004' },
+ ]
+ end
+
+ # Return complete url to build page
+ #
+ # Ex.
+ # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
+ #
+ def build_page(sha, ref)
+ url = [mock_service_url,
+ "#{project.namespace.path}/#{project.path}/status/#{sha}"]
+
+ URI.join(*url).to_s
+ end
+
+ # Return string with build status or :error symbol
+ #
+ # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
+ #
+ #
+ # Ex.
+ # @service.commit_status('13be4ac', 'master')
+ # # => 'success'
+ #
+ # @service.commit_status('2abe4ac', 'dev')
+ # # => 'running'
+ #
+ #
+ def commit_status(sha, ref)
+ response = HTTParty.get(commit_status_path(sha), verify: false)
+ read_commit_status(response)
+ rescue Errno::ECONNREFUSED
+ :error
+ end
+
+ def commit_status_path(sha)
+ url = [mock_service_url,
+ "#{project.namespace.path}/#{project.path}/status/#{sha}.json"]
+
+ URI.join(*url).to_s
+ end
+
+ def read_commit_status(response)
+ return :error unless response.code == 200 || response.code == 404
+
+ status = if response.code == 404
+ 'pending'
+ else
+ response['status']
+ end
+
+ if status.present? && ALLOWED_STATES.include?(status)
+ status
+ else
+ :error
+ end
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index f77d2d7c60b..da7496573ef 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -13,11 +13,11 @@ class SlackService < ChatNotificationService
def help
'This service sends notifications about projects events to Slack channels.<br />
- To setup this service:
+ To set up this service:
<ol>
- <li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li>
- <li>Paste the <strong>Webhook URL</strong> into the field below. </li>
- <li>Select events below to enable notifications. The channel and username are optional. </li>
+ <li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event.</li>
+ <li>Paste the <strong>Webhook URL</strong> into the field below.</li>
+ <li>Select events below to enable notifications. The <strong>Channel name</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
@@ -27,14 +27,14 @@ class SlackService < ChatNotificationService
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
- { type: 'text', name: 'username', placeholder: 'username' },
+ { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
- "#general"
+ "Channel name (e.g. general)"
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index cd2568ad445..0dbf246c3a4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -109,9 +109,7 @@ class Repository
offset: offset,
after: after,
before: before,
- # --follow doesn't play well with --skip. See:
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
- follow: false,
+ follow: path.present?,
skip_merges: skip_merges
}
@@ -746,136 +744,63 @@ class Repository
@tags ||= raw_repository.tags
end
- # rubocop:disable Metrics/ParameterLists
- def commit_dir(
- user, path,
- message:, branch_name:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- check_tree_entry_for_dir(branch_name, path)
-
- if start_branch_name
- start_project.repository.
- check_tree_entry_for_dir(start_branch_name, path)
- end
+ def create_dir(user, path, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :create_dir, file_path: path }]
- commit_file(
- user,
- "#{path}/.gitkeep",
- '',
- message: message,
- branch_name: branch_name,
- update: false,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project)
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
- # rubocop:disable Metrics/ParameterLists
- def commit_file(
- user, path, content,
- message:, branch_name:, update: true,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- unless update
- error_message = "Filename already exists; update not allowed"
+ def create_file(user, path, content, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :create, file_path: path, content: content }]
- if tree_entry_at(branch_name, path)
- raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
- end
+ multi_action(**options)
+ end
- if start_branch_name &&
- start_project.repository.tree_entry_at(start_branch_name, path)
- raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
- end
- end
+ def update_file(user, path, content, **options)
+ previous_path = options.delete(:previous_path)
+ action = previous_path && previous_path != path ? :move : :update
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: :create,
- file_path: path,
- content: content }])
- end
- # rubocop:enable Metrics/ParameterLists
+ options[:user] = user
+ options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
- # rubocop:disable Metrics/ParameterLists
- def update_file(
- user, path, content,
- message:, branch_name:, previous_path:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- action = if previous_path && previous_path != path
- :move
- else
- :update
- end
-
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: action,
- file_path: path,
- content: content,
- previous_path: previous_path }])
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
- # rubocop:disable Metrics/ParameterLists
- def remove_file(
- user, path,
- message:, branch_name:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: :delete,
- file_path: path }])
+ def delete_file(user, path, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :delete, file_path: path }]
+
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists
def multi_action(
user:, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
+
GitOperationService.new(user, self).with_branch(
branch_name,
start_branch_name: start_branch_name,
start_project: start_project) do |start_commit|
- index = rugged.index
- parents = if start_commit
- index.read_tree(start_commit.raw_commit.tree)
- [start_commit.sha]
- else
- []
- end
+ index = Gitlab::Git::Index.new(raw_repository)
- actions.each do |act|
- git_action(index, act)
+ if start_commit
+ index.read_tree(start_commit.raw_commit.tree)
+ parents = [start_commit.sha]
+ else
+ parents = []
+ end
+
+ actions.each do |options|
+ index.public_send(options.delete(:action), options)
end
options = {
- tree: index.write_tree(rugged),
+ tree: index.write_tree,
message: message,
parents: parents
}
@@ -1166,30 +1091,6 @@ class Repository
blob_data_at(sha, '.gitlab-ci.yml')
end
- protected
-
- def tree_entry_at(branch_name, path)
- branch_exists?(branch_name) &&
- # tree_entry is private
- raw_repository.send(:tree_entry, commit(branch_name), path)
- end
-
- def check_tree_entry_for_dir(branch_name, path)
- return unless branch_exists?(branch_name)
-
- entry = tree_entry_at(branch_name, path)
-
- return unless entry
-
- if entry[:type] == :blob
- raise Gitlab::Git::Repository::InvalidBlobName.new(
- "Directory already exists as a file")
- else
- raise Gitlab::Git::Repository::InvalidBlobName.new(
- "Directory already exists")
- end
- end
-
private
def blob_data_at(sha, path)
@@ -1200,58 +1101,6 @@ class Repository
blob.data
end
- def git_action(index, action)
- path = normalize_path(action[:file_path])
-
- if action[:action] == :move
- previous_path = normalize_path(action[:previous_path])
- end
-
- case action[:action]
- when :create, :update, :move
- mode =
- case action[:action]
- when :update
- index.get(path)[:mode]
- when :move
- index.get(previous_path)[:mode]
- end
- mode ||= 0o100644
-
- index.remove(previous_path) if action[:action] == :move
-
- content = if action[:encoding] == 'base64'
- Base64.decode64(action[:content])
- else
- action[:content]
- end
-
- detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
-
- unless detect && detect[:type] == :binary
- # When writing to the repo directly as we are doing here,
- # the `core.autocrlf` config isn't taken into account.
- content.gsub!("\r\n", "\n") if self.autocrlf
- end
-
- oid = rugged.write(content, :blob)
-
- index.add(path: path, oid: oid, mode: mode)
- when :delete
- index.remove(path)
- end
- end
-
- def normalize_path(path)
- pathname = Gitlab::Git::PathHelper.normalize_path(path)
-
- if pathname.each_filename.include?('..')
- raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
- end
-
- pathname.to_s
- end
-
def refs_directory_exists?
return false unless path_with_namespace
diff --git a/app/models/service.rb b/app/models/service.rb
index facaaf9b331..3ef4cbead10 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -210,7 +210,7 @@ class Service < ActiveRecord::Base
end
def self.available_services_names
- %w[
+ service_names = %w[
asana
assembla
bamboo
@@ -238,6 +238,9 @@ class Service < ActiveRecord::Base
slack
teamcity
]
+ service_names << 'mock_ci' if Rails.env.development?
+
+ service_names.sort_by(&:downcase)
end
def self.build_from_template(project_id, template)
diff --git a/app/models/user.rb b/app/models/user.rb
index fada0e567f0..40264401b53 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -81,7 +81,6 @@ class User < ActiveRecord::Base
has_many :authorized_projects, through: :project_authorizations, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id
- has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id
@@ -99,6 +98,11 @@ class User < ActiveRecord::Base
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+ # Issues that a user owns are expected to be moved to the "ghost" user before
+ # the user is destroyed. If the user owns any issues during deletion, this
+ # should be treated as an exceptional condition.
+ has_many :issues, dependent: :restrict_with_exception, foreign_key: :author_id
+
#
# Validations
#
@@ -120,6 +124,7 @@ class User < ActiveRecord::Base
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+ validate :ghost_users_must_be_blocked
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
@@ -334,9 +339,15 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
- (?<user>#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR})
+ (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
}x
end
+
+ # Return (create if necessary) the ghost user. The ghost user
+ # owns records previously belonging to deleted users.
+ def ghost
+ User.find_by_ghost(true) || create_ghost_user
+ end
end
#
@@ -435,6 +446,12 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ def ghost_users_must_be_blocked
+ if ghost? && !blocked?
+ errors.add(:ghost, 'cannot be enabled for a user who is not blocked.')
+ end
+ end
+
def update_emails_with_primary_email
primary_email_record = emails.find_by(email: email)
if primary_email_record
@@ -999,4 +1016,40 @@ class User < ActiveRecord::Base
super
end
end
+
+ def self.create_ghost_user
+ # Since we only want a single ghost user in an instance, we use an
+ # exclusive lease to ensure than this block is never run concurrently.
+ lease_key = "ghost_user_creation"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. To prevent hammering Redis too
+ # much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ # Recheck if a ghost user is already present. One might have been
+ # added between the time we last checked (first line of this method)
+ # and the time we acquired the lock.
+ ghost_user = User.find_by_ghost(true)
+ return ghost_user if ghost_user.present?
+
+ uniquify = Uniquify.new
+
+ username = uniquify.string("ghost") { |s| User.find_by_username(s) }
+
+ email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s|
+ User.find_by_email(s)
+ end
+
+ bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
+
+ User.create(
+ username: username, password: Devise.friendly_token, bio: bio,
+ email: email, name: "Ghost User", state: :blocked, ghost: true
+ )
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 03a2499e263..229846e368c 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -3,6 +3,14 @@ class UserPolicy < BasePolicy
def rules
can! :read_user if @user || !restricted_public_level?
+
+ if @user
+ if @user.admin? || @subject == @user
+ can! :destroy_user
+ end
+
+ cannot! :destroy_user if @subject.ghost?
+ end
end
def restricted_public_level?
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
index 858de5f0538..083ffdc634c 100644
--- a/app/services/files/create_dir_service.rb
+++ b/app/services/files/create_dir_service.rb
@@ -1,7 +1,7 @@
module Files
class CreateDirService < Files::BaseService
def commit
- repository.commit_dir(
+ repository.create_dir(
current_user,
@file_path,
message: @commit_message,
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 88dd7bbaedb..65b5537fb68 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,13 +1,12 @@
module Files
class CreateService < Files::BaseService
def commit
- repository.commit_file(
+ repository.create_file(
current_user,
@file_path,
@file_content,
message: @commit_message,
branch_name: @target_branch,
- update: false,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
@@ -17,6 +16,10 @@ module Files
def validate
super
+ if @file_content.nil?
+ raise_error("You must provide content.")
+ end
+
if @file_path =~ Gitlab::Regex.directory_traversal_regex
raise_error(
'Your changes could not be committed, because the file name ' +
diff --git a/app/services/files/destroy_service.rb b/app/services/files/destroy_service.rb
index c3be806a42d..e294659bc98 100644
--- a/app/services/files/destroy_service.rb
+++ b/app/services/files/destroy_service.rb
@@ -1,7 +1,7 @@
module Files
class DestroyService < Files::BaseService
def commit
- repository.remove_file(
+ repository.delete_file(
current_user,
@file_path,
message: @commit_message,
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index af6da5b9d56..0609c6219e7 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -2,6 +2,8 @@ module Files
class MultiService < Files::BaseService
class FileChangedError < StandardError; end
+ ACTIONS = %w[create update delete move].freeze
+
def commit
repository.multi_action(
user: current_user,
@@ -21,10 +23,19 @@ module Files
super
params[:actions].each_with_index do |action, index|
+ if ACTIONS.include?(action[:action].to_s)
+ action[:action] = action[:action].to_sym
+ else
+ raise_error("Unknown action type `#{action[:action]}`.")
+ end
+
unless action[:file_path].present?
raise_error("You must specify a file_path.")
end
+ action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
+ action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
+
regex_check(action[:file_path])
regex_check(action[:previous_path]) if action[:previous_path]
@@ -43,8 +54,6 @@ module Files
validate_delete(action)
when :move
validate_move(action, index)
- else
- raise_error("Unknown action type `#{action[:action]}`.")
end
end
end
@@ -92,6 +101,20 @@ module Files
if repository.blob_at_branch(params[:branch], action[:file_path])
raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
end
+
+ if action[:content].nil?
+ raise_error("You must provide content.")
+ end
+ end
+
+ def validate_update(action)
+ if action[:content].nil?
+ raise_error("You must provide content.")
+ end
+
+ if file_has_changed?
+ raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
+ end
end
def validate_delete(action)
@@ -114,11 +137,5 @@ module Files
params[:actions][index][:content] = blob.data
end
end
-
- def validate_update(action)
- if file_has_changed?
- raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
- end
- end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a71fe61a4b6..54e1aaf3f67 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -18,6 +18,10 @@ module Files
def validate
super
+ if @file_content.nil?
+ raise_error("You must provide content.")
+ end
+
if file_has_changed?
raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 2e2d7f884ac..497fdb09cdc 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -18,7 +18,8 @@ module Groups
end
group.children.each do |group|
- DestroyService.new(group, current_user).async_execute
+ # This needs to be synchronous since the namespace gets destroyed below
+ DestroyService.new(group, current_user).execute
end
group.really_destroy!
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index bc0653cb634..833da5bc5d1 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -7,7 +7,7 @@ module Users
end
def execute(user, options = {})
- unless current_user.admin? || current_user == user
+ unless Ability.allowed?(current_user, :destroy_user, user)
raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!"
end
@@ -26,6 +26,8 @@ module Users
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
end
+ move_issues_to_ghost_user(user)
+
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
namespace = user.namespace
user_data = user.destroy
@@ -33,5 +35,22 @@ module Users
user_data
end
+
+ private
+
+ def move_issues_to_ghost_user(user)
+ # Block the user before moving issues to prevent a data race.
+ # If the user creates an issue after `move_issues_to_ghost_user`
+ # runs and before the user is destroyed, the destroy will fail with
+ # an exception. We block the user so that issues can't be created
+ # after `move_issues_to_ghost_user` runs and before the destroy happens.
+ user.block
+
+ ghost_user = User.ghost
+
+ user.issues.update_all(author_id: ghost_user.id)
+
+ user.reload
+ end
end
end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 86f317dcd18..e84944ed411 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -27,10 +27,6 @@ class ArtifactUploader < GitlabUploader
File.join(self.class.artifacts_cache_path, @build.artifacts_path)
end
- def file_storage?
- self.class.storage == CarrierWave::Storage::File
- end
-
def filename
file.try(:filename)
end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index cfcb877cc3e..6aa1f5a8c50 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -4,6 +4,6 @@ class AttachmentUploader < GitlabUploader
storage :file
def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 265cea2d2c6..b4c393c6f2c 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -4,7 +4,7 @@ class AvatarUploader < GitlabUploader
storage :file
def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def exists?
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 23b7318827c..0d2edaeff3b 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -4,15 +4,12 @@ class FileUploader < GitlabUploader
storage :file
- attr_accessor :project, :secret
+ attr_accessor :project
+ attr_reader :secret
def initialize(project, secret = nil)
@project = project
- @secret = secret || self.class.generate_secret
- end
-
- def base_dir
- "uploads"
+ @secret = secret || generate_secret
end
def store_dir
@@ -23,10 +20,6 @@ class FileUploader < GitlabUploader
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def secure_url
- File.join("/uploads", @secret, file.filename)
- end
-
def to_markdown
to_h[:markdown]
end
@@ -35,17 +28,23 @@ class FileUploader < GitlabUploader
filename = image_or_video? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
- markdown = "[#{escaped_filename}](#{self.secure_url})"
+ markdown = "[#{escaped_filename}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
{
alt: filename,
- url: self.secure_url,
+ url: secure_url,
markdown: markdown
}
end
- def self.generate_secret
+ private
+
+ def generate_secret
SecureRandom.hex
end
+
+ def secure_url
+ File.join('/uploads', @secret, file.filename)
+ end
end
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 02d7c601d6c..bd7de4ed562 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -1,4 +1,14 @@
class GitlabUploader < CarrierWave::Uploader::Base
+ def self.base_dir
+ 'uploads'
+ end
+
+ delegate :base_dir, to: :class
+
+ def file_storage?
+ self.class.storage == CarrierWave::Storage::File
+ end
+
# Reduce disk IO
def move_to_cache
true
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index bee311583ea..7635c20ab3a 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -27,6 +27,8 @@ module UploaderHelper
extension_match?(DANGEROUS_EXT)
end
+ private
+
def extension_match?(extensions)
return false unless file
@@ -40,8 +42,4 @@ module UploaderHelper
extensions.include?(extension.downcase)
end
-
- def file_storage?
- self.class.storage == CarrierWave::Storage::File
- end
end
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index deb62845e1c..d4d166ab7b6 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -15,6 +15,8 @@
%td
= runner.description
%td
+ = runner.version
+ %td
- if runner.shared?
n/a
- else
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index d725e477044..7d26864d0f3 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -67,6 +67,7 @@
%th Type
%th Runner token
%th Description
+ %th Version
%th Projects
%th Jobs
%th Tags
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index 3b5c713ac2d..a756cb7243a 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -34,7 +34,7 @@
- if user.access_locked?
%li
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- - if user.can_be_removed?
+ - if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider
%li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 76b1291fe10..840d843f069 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -173,7 +173,7 @@
.panel-heading
Remove user
.panel-body
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p Deleting a user has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
@@ -189,3 +189,6 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete this user.
+ - else
+ %p
+ You don't have access to delete this user.
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index dc76599b776..0dbb0ca6958 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -4,7 +4,7 @@
.nav-block
- if current_user
.controls
- = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
+ = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 4f36a4a1c73..b82b933c3ad 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -5,6 +5,8 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
+.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
+
- if @projects.any? || params[:filter_projects]
= render 'dashboard/projects_head'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 5a44ec45b7b..30e63d991bb 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
index 71cc4d87b1f..c442cf056c3 100644
--- a/app/views/groups/_activities.html.haml
+++ b/app/views/groups/_activities.html.haml
@@ -4,7 +4,7 @@
.nav-block
- if current_user
.controls
- = link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn' do
+ = link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a4f4079d556..02fb47ec981 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -115,7 +115,7 @@
%h4.prepend-top-0.danger-title
Remove account
.col-lg-9
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p
Deleting an account has the following effects:
%ul
@@ -131,4 +131,7 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete your account.
+ - else
+ %p
+ You don't have access to delete this user.
.append-bottom-default
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index b10f5fc08e2..903b957c26b 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -101,5 +101,3 @@
$("#created-personal-access-token").click(function() {
this.select();
});
-
- $("#created-personal-access-token").effect('highlight', { color: '#ffff99' }, 2000);
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 0ea733cb978..4268337fd6d 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -4,7 +4,7 @@
.nav-block.activity-filter-block
- if current_user
.controls
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index d1f7f65bf53..c48d9dd982c 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -19,10 +19,10 @@
= line_content
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "##{line_old}"
+ %a{ href: "##{line_old}", data: { linenumber: line_old } }
= line_content
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
- = link_to raw(line_new), "##{line_new}"
+ %a{ href: "##{line_new}", data: { linenumber: line_new } }
= line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index f5ca9607823..b3bc6010efb 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -8,7 +8,6 @@
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
- %script#js-board-list-card{ type: "text/x-template" }= render "projects/boards/components/card"
= render "projects/issues/head"
diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml
deleted file mode 100644
index 891c2c46251..00000000000
--- a/app/views/projects/boards/components/_card.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
- ":index" => "index",
- ":data-issue-id" => "issue.id",
- "@mousedown" => "mouseDown",
- "@mousemove" => "mouseMove",
- "@mouseup" => "showIssue($event)" }
- %issue-card-inner{ ":list" => "list",
- ":issue" => "issue",
- ":issue-link-base" => "issueLinkBase",
- ":root-path" => "rootPath" }
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index e63bdb38bd8..d3c3e40d518 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -12,12 +12,16 @@
.form-group
= label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
+ = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name'
.help-block.text-danger.js-branch-name-error
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+ = hidden_field_tag :ref, params[:ref] || @project.default_branch
+ = dropdown_tag(params[:ref] || @project.default_branch,
+ options: { toggle_class: 'js-branch-select wide',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
+ data: { selected: params[:ref] || @project.default_branch, field_name: 'ref' } })
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index 27e81c2bec3..7eb17e887e7 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,4 +1,4 @@
-.content-block.build-header
+.content-block.build-header.top-area
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Job
@@ -16,7 +16,10 @@
- if @build.user
= render "user"
= time_ago_with_tooltip(@build.created_at)
- - if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
+ .nav-controls
+ - if can?(current_user, :create_issue, @project) && @build.failed?
+ = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+ - if can?(current_user, :update_build, @build) && @build.retryable?
+ = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
deleted file mode 100644
index 3475fa5f960..00000000000
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ /dev/null
@@ -1,92 +0,0 @@
-- status = pipeline.status
-- show_commit = local_assigns.fetch(:show_commit, true)
-- show_branch = local_assigns.fetch(:show_branch, true)
-
-%tr.commit
- %td.commit-link
- = render 'ci/status/badge', status: pipeline.detailed_status(current_user)
-
- %td
- = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
- %span.pipeline-id ##{pipeline.id}
- %span by
- - if pipeline.user
- = user_avatar(user: pipeline.user, size: 20)
- - else
- %span.api.monospace API
- - if pipeline.latest?
- %span.label.label-success.has-tooltip{ title: 'Latest pipeline for this branch' } latest
- - if pipeline.triggered?
- %span.label.label-primary triggered
- - if pipeline.yaml_errors.present?
- %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid
- - if pipeline.builds.any?(&:stuck?)
- %span.label.label-warning stuck
-
- %td.branch-commit
- - if pipeline.ref && show_branch
- .icon-container
- = pipeline.tag? ? icon('tag') : icon('code-fork')
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
- - if show_commit
- .icon-container.commit-icon
- = custom_icon("icon_commit")
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
-
- %p.commit-title
- - if commit = pipeline.commit
- = author_avatar(commit, size: 20)
- = link_to_gfm truncate(commit.title, length: 60, escape: false), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message"
- - else
- Cant find HEAD commit for this branch
-
- %td
- = render 'shared/mini_pipeline_graph', pipeline: pipeline, klass: 'js-mini-pipeline-graph'
-
- %td
- - if pipeline.duration
- %p.duration
- = custom_icon("icon_timer")
- = duration_in_numbers(pipeline.duration)
- - if pipeline.finished_at
- %p.finished-at
- = icon("calendar")
- #{time_ago_with_tooltip(pipeline.finished_at, short_format: false)}
-
- %td.pipeline-actions.hidden-xs
- .controls.pull-right
- - artifacts = pipeline.builds.latest.with_artifacts_not_expired
- - actions = pipeline.manual_actions
- - if artifacts.present? || actions.any?
- .btn-group.inline
- - if actions.any?
- .btn-group
- %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual pipeline', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual pipeline' }
- = custom_icon('icon_play')
- = icon('caret-down', 'aria-hidden' => 'true')
- %ul.dropdown-menu.dropdown-menu-align-right
- - actions.each do |build|
- %li
- = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
- = custom_icon('icon_play')
- %span= build.name
- - if artifacts.present?
- .btn-group
- %button.dropdown-toggle.btn.btn-default.build-artifacts.has-tooltip.js-pipeline-dropdown-download{ type: 'button', title: 'Artifacts', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Artifacts' }
- = icon("download")
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - artifacts.each do |build|
- %li
- = link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow', download: '' do
- = icon("download")
- %span Download '#{build.name}' artifacts
-
- - if can?(current_user, :update_pipeline, pipeline.project)
- .cancel-retry-btns.inline
- - if pipeline.retryable?
- = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: 'Retry', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Retry' , method: :post do
- = icon("repeat")
- - if pipeline.cancelable?
- = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: 'Cancel', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Cancel' , method: :post do
- = icon("remove")
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 1dbfe830d52..f809c52c367 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -10,10 +10,10 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } }
= old_path
&rarr;
- %strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= new_path
- else
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b9300efd04f..83ae9fd10ec 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -120,7 +120,7 @@
.form-group
- if @project.avatar?
.avatar-container.s160
- = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160')
%p.light
- if @project.avatar_in_git
Project avatar in repository: #{ @project.avatar_in_git }
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 55202725b9e..14a270a3039 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -9,7 +9,11 @@
.form-group
= f.label :ref, 'Create for', class: 'control-label'
.col-sm-10
- = f.text_field :ref, required: true, tabindex: 2, class: 'form-control js-branch-name ui-autocomplete-input', autocomplete: :false, id: :ref
+ = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
+ = dropdown_tag(params[:ref] || @project.default_branch,
+ options: { toggle_class: 'js-branch-select wide',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
+ data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block Existing branch name, tag
.form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index efb207b9916..02b7b2447ed 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -17,7 +17,7 @@
%strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
- pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE,
+ pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
title: 'Please choose a group name with no special characters.'
- if parent
= f.hidden_field :parent_id, value: parent.id
diff --git a/app/views/shared/icons/_icon_customization.svg b/app/views/shared/icons/_icon_customization.svg
new file mode 100644
index 00000000000..eb1f8ba129b
--- /dev/null
+++ b/app/views/shared/icons/_icon_customization.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112 90" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><rect width="112" height="90" fill="#fff" rx="6"/><path fill="#eee" fill-rule="nonzero" d="m4 6.01v77.98c0 1.11.899 2.01 2 2.01h100c1.105 0 2-.898 2-2.01v-77.98c0-1.11-.899-2.01-2-2.01h-100c-1.105 0-2 .898-2 2.01m-4 0c0-3.319 2.686-6.01 6-6.01h100c3.315 0 6 2.694 6 6.01v77.98c0 3.319-2.686 6.01-6 6.01h-100c-3.315 0-6-2.694-6-6.01v-77.98"/><g transform="translate(26 35)"><rect width="4" height="39" x="5" fill="#eee" rx="2" id="0"/><rect width="4" height="21" x="5" y="18" fill="#fef0ea" rx="2"/><circle cx="7" cy="13" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 20c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(49 35)"><use xlink:href="#0"/><rect width="4" height="21" x="5" y="18" fill="#b5a7dd" rx="2"/><circle cx="7" cy="25" r="5" fill="#fff"/><path fill="#6b4fbb" fill-rule="nonzero" d="m7 32c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(72 33)"><rect width="4" height="39" x="5" y="2" fill="#eee" rx="2"/><rect width="4" height="34" x="5" y="7" fill="#fef0ea" rx="2"/><circle cx="7" cy="7" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 14c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g fill="#6b4fbb"><circle cx="13.5" cy="11.5" r="2.5"/><circle cx="23.5" cy="11.5" r="2.5" opacity=".5"/><circle cx="33.5" cy="11.5" r="2.5" opacity=".5"/></g><path fill="#eee" d="m0 19h111v4h-111z"/></g></svg>
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 8e04b50bb8a..62f09cc2dc1 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -82,7 +82,7 @@
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{title}}
- .pull-right
+ .pull-right.filter-dropdown-container
= render 'shared/sort_dropdown'
- if @bulk_edit
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 3f7f1a86b9f..6c730e16f67 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -173,7 +173,7 @@
:javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}");
- new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
+ new MilestoneSelect('{"full_path":"#{@project.full_path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
gl.Subscription.bindAll('.subscription');
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index dc2fea450bd..c130f3d9e17 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -98,6 +98,7 @@
Snippets
%div{ class: container_class }
+ .user-callout{ 'callout-svg' => custom_icon('icon_customization') }
.tab-content
#activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs
diff --git a/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml b/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml
new file mode 100644
index 00000000000..4a1a199673c
--- /dev/null
+++ b/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml
@@ -0,0 +1,4 @@
+---
+title: Deleting a user doesn't delete issues they've created/are assigned to
+merge_request: 7393
+author:
diff --git a/changelogs/unreleased/14748-runner-version-in-admin-views.yml b/changelogs/unreleased/14748-runner-version-in-admin-views.yml
new file mode 100644
index 00000000000..2478a81c824
--- /dev/null
+++ b/changelogs/unreleased/14748-runner-version-in-admin-views.yml
@@ -0,0 +1,4 @@
+---
+title: Add runner version to /admin/runners view
+merge_request: 8733
+author: Jonathon Reinhart
diff --git a/changelogs/unreleased/1937-https-clone-url-username.yml b/changelogs/unreleased/1937-https-clone-url-username.yml
new file mode 100644
index 00000000000..fa89d94e0f3
--- /dev/null
+++ b/changelogs/unreleased/1937-https-clone-url-username.yml
@@ -0,0 +1,4 @@
+---
+title: Add the Username to the HTTP(S) clone URL of a Repository
+merge_request: 9347
+author: Jan Christophersen
diff --git a/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml b/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml
new file mode 100644
index 00000000000..f7c856040e0
--- /dev/null
+++ b/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml
@@ -0,0 +1,4 @@
+---
+title: Make Git history follow renames again by performing the --skip in Ruby
+merge_request:
+author:
diff --git a/changelogs/unreleased/25920-create-issue-from-failing-build.yml b/changelogs/unreleased/25920-create-issue-from-failing-build.yml
new file mode 100644
index 00000000000..580d1074aa7
--- /dev/null
+++ b/changelogs/unreleased/25920-create-issue-from-failing-build.yml
@@ -0,0 +1,4 @@
+---
+title: Add button to create issue for failing build
+merge_request: 9391
+author: Alex Sanford
diff --git a/changelogs/unreleased/27267-events-project-query-performance-regression.yml b/changelogs/unreleased/27267-events-project-query-performance-regression.yml
new file mode 100644
index 00000000000..a1697b57eac
--- /dev/null
+++ b/changelogs/unreleased/27267-events-project-query-performance-regression.yml
@@ -0,0 +1,4 @@
+---
+title: 'Add performance query regression fix for !9088 affecting #27267'
+merge_request:
+author:
diff --git a/changelogs/unreleased/27840-improve-search-bar-experience.yml b/changelogs/unreleased/27840-improve-search-bar-experience.yml
new file mode 100644
index 00000000000..87b1f0c5572
--- /dev/null
+++ b/changelogs/unreleased/27840-improve-search-bar-experience.yml
@@ -0,0 +1,4 @@
+---
+title: Enhanced filter issues layout for better mobile experiance
+merge_request: 9280
+author: Pratik Borsadiya
diff --git a/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml b/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml
new file mode 100644
index 00000000000..800e0389c86
--- /dev/null
+++ b/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml
@@ -0,0 +1,4 @@
+---
+title: Replace setInterval with setTimeout to prevent highly frequent requests
+merge_request: 9271
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml b/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml
new file mode 100644
index 00000000000..faf1e89ed94
--- /dev/null
+++ b/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml
@@ -0,0 +1,4 @@
+---
+title: Remove markup that was showing in tooltip for renamed files
+merge_request: 9374
+author:
diff --git a/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml b/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml
new file mode 100644
index 00000000000..6fc89fd91dd
--- /dev/null
+++ b/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes includes line number during unfold copy n paste in parallel diff view
+merge_request: 9365
+author:
diff --git a/changelogs/unreleased/28723-consistent-handling-indexof.yml b/changelogs/unreleased/28723-consistent-handling-indexof.yml
new file mode 100644
index 00000000000..95d6181d5fa
--- /dev/null
+++ b/changelogs/unreleased/28723-consistent-handling-indexof.yml
@@ -0,0 +1,4 @@
+---
+title: Keep consistent in handling indexOf results
+merge_request: 9531
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/api-remove-owned-groups.yml b/changelogs/unreleased/api-remove-owned-groups.yml
new file mode 100644
index 00000000000..cf0301b7fe0
--- /dev/null
+++ b/changelogs/unreleased/api-remove-owned-groups.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Remove /groups/owned endpoint'
+merge_request: 9505
+author: Robert Schilling
diff --git a/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml b/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml
new file mode 100644
index 00000000000..e646a6a17b7
--- /dev/null
+++ b/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add Runner's registration/deletion v4 API
+merge_request: 9246
+author:
diff --git a/changelogs/unreleased/fix-mr-size-with-over-100-files.yml b/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
deleted file mode 100644
index eecf3c99a75..00000000000
--- a/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix MR changes tab size count when there are over 100 files in the diff
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_24815.yml b/changelogs/unreleased/issue_24815.yml
new file mode 100644
index 00000000000..916e47d36a9
--- /dev/null
+++ b/changelogs/unreleased/issue_24815.yml
@@ -0,0 +1,4 @@
+---
+title: Fix issuable stale object error handler for js when updating tasklists
+merge_request:
+author:
diff --git a/changelogs/unreleased/mock-ci-service.yml b/changelogs/unreleased/mock-ci-service.yml
new file mode 100644
index 00000000000..24c6366177f
--- /dev/null
+++ b/changelogs/unreleased/mock-ci-service.yml
@@ -0,0 +1,4 @@
+---
+title: Add Mock CI service/integration for development
+merge_request:
+author:
diff --git a/changelogs/unreleased/moving-issue-with-two-list-labels.yml b/changelogs/unreleased/moving-issue-with-two-list-labels.yml
new file mode 100644
index 00000000000..d5ea81e3810
--- /dev/null
+++ b/changelogs/unreleased/moving-issue-with-two-list-labels.yml
@@ -0,0 +1,4 @@
+---
+title: Removes label when moving issue to another list that it is currently in
+merge_request:
+author:
diff --git a/changelogs/unreleased/mr-diff-comment-button.yml b/changelogs/unreleased/mr-diff-comment-button.yml
new file mode 100644
index 00000000000..1dc6ed1c495
--- /dev/null
+++ b/changelogs/unreleased/mr-diff-comment-button.yml
@@ -0,0 +1,4 @@
+---
+title: Improved diff comment button UX
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-jquery-ui-plugins.yml b/changelogs/unreleased/remove-jquery-ui-plugins.yml
new file mode 100644
index 00000000000..c768f702ba2
--- /dev/null
+++ b/changelogs/unreleased/remove-jquery-ui-plugins.yml
@@ -0,0 +1,4 @@
+---
+title: Removed jQuery UI highlight & autocomplete
+merge_request:
+author:
diff --git a/changelogs/unreleased/rss-btn-alignment-fix.yml b/changelogs/unreleased/rss-btn-alignment-fix.yml
new file mode 100644
index 00000000000..c8f57ec0b7c
--- /dev/null
+++ b/changelogs/unreleased/rss-btn-alignment-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed RSS button alignment on activity pages
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml b/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml
new file mode 100644
index 00000000000..57f1474093a
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml
@@ -0,0 +1,4 @@
+---
+title: Bump Hashie to 3.5.5 and omniauth to 1.4.2 to eliminate warning noise
+merge_request:
+author:
diff --git a/changelogs/unreleased/ssh-key-paste.yml b/changelogs/unreleased/ssh-key-paste.yml
new file mode 100644
index 00000000000..1e34ef60f6e
--- /dev/null
+++ b/changelogs/unreleased/ssh-key-paste.yml
@@ -0,0 +1,4 @@
+---
+title: SSH key field updates title after pasting key
+merge_request:
+author:
diff --git a/changelogs/unreleased/unified-member-api-response.yml b/changelogs/unreleased/unified-member-api-response.yml
new file mode 100644
index 00000000000..0a60b4d46a3
--- /dev/null
+++ b/changelogs/unreleased/unified-member-api-response.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Return 400 for all validation erros in the mebers API'
+merge_request: 9523
+author: Robert Schilling
diff --git a/changelogs/unreleased/update-vue-2-1.yml b/changelogs/unreleased/update-vue-2-1.yml
new file mode 100644
index 00000000000..acc42bf00b1
--- /dev/null
+++ b/changelogs/unreleased/update-vue-2-1.yml
@@ -0,0 +1,4 @@
+---
+title: update Vue to v2.1.10
+merge_request: 9386
+author:
diff --git a/changelogs/unreleased/user-callouts.yml b/changelogs/unreleased/user-callouts.yml
new file mode 100644
index 00000000000..f6ce06a3d8f
--- /dev/null
+++ b/changelogs/unreleased/user-callouts.yml
@@ -0,0 +1,4 @@
+---
+title: Added user callouts to the projects dashboard and user profile
+merge_request:
+author:
diff --git a/config/webpack.config.js b/config/webpack.config.js
index e754f68553a..a71ec0c5f52 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -87,7 +87,7 @@ var config = {
'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap',
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- 'vue$': IS_PRODUCTION ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
+ 'vue$': 'vue/dist/vue.common.js',
}
}
}
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 747901dd634..aea0a72b633 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -155,17 +155,9 @@ class Gitlab::Seeder::CycleAnalytics
issue.project.repository.add_branch(@user, branch_name, 'master')
- options = {
- committer: issue.project.repository.user_to_committer(@user),
- author: issue.project.repository.user_to_committer(@user),
- commit: { message: "Commit for ##{issue.iid}", branch: branch_name, update_ref: true },
- file: { content: "content", path: filename, update: false }
- }
-
- commit_sha = Gitlab::Git::Blob.commit(issue.project.repository, options)
+ commit_sha = issue.project.repository.create_file(@user, filename, "content", options, message: "Commit for ##{issue.iid}", branch_name: branch_name)
issue.project.repository.commit(commit_sha)
-
GitPushService.new(issue.project,
@user,
oldrev: issue.project.repository.commit("master").sha,
diff --git a/db/fixtures/development/19_nested_groups.rb b/db/fixtures/development/19_nested_groups.rb
new file mode 100644
index 00000000000..d8dddc3fee9
--- /dev/null
+++ b/db/fixtures/development/19_nested_groups.rb
@@ -0,0 +1,69 @@
+require './spec/support/sidekiq'
+
+def create_group_with_parents(user, full_path)
+ parent_path = nil
+ group = nil
+
+ until full_path.blank?
+ path, _, full_path = full_path.partition('/')
+
+ if parent_path
+ parent = Group.find_by_full_path(parent_path)
+
+ parent_path += '/'
+ parent_path += path
+
+ group = Groups::CreateService.new(user, path: path, parent_id: parent.id).execute
+ else
+ parent_path = path
+
+ group = Group.find_by_full_path(parent_path) ||
+ Groups::CreateService.new(user, path: path).execute
+ end
+ end
+
+ group
+end
+
+Sidekiq::Testing.inline! do
+ Gitlab::Seeder.quiet do
+ project_urls = [
+ 'https://android.googlesource.com/platform/hardware/broadcom/libbt.git',
+ 'https://android.googlesource.com/platform/hardware/broadcom/wlan.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/bootloader/intel/edison-u-boot.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/broadcom.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/freescale.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/imagination.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/intel.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.1.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.4.git'
+ ]
+
+ user = User.admins.first
+
+ project_urls.each_with_index do |url, i|
+ full_path = url.sub('https://android.googlesource.com/', '')
+ full_path = full_path.sub(/\.git\z/, '')
+ full_path, _, project_path = full_path.rpartition('/')
+ group = Group.find_by_full_path(full_path) || create_group_with_parents(user, full_path)
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ path: project_path,
+ name: project_path,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample
+ }
+
+ project = Projects::CreateService.new(user, params).execute
+ project.send(:_run_after_commit_queue)
+
+ if project.valid?
+ print '.'
+ else
+ print 'F'
+ end
+ end
+ end
+end
diff --git a/db/migrate/20160610201627_migrate_users_notification_level.rb b/db/migrate/20160610201627_migrate_users_notification_level.rb
index ce4f00e25fa..cd8b505de9f 100644
--- a/db/migrate/20160610201627_migrate_users_notification_level.rb
+++ b/db/migrate/20160610201627_migrate_users_notification_level.rb
@@ -1,4 +1,6 @@
class MigrateUsersNotificationLevel < ActiveRecord::Migration
+ DOWNTIME = false
+
# Migrates only users who changed their default notification level :participating
# creating a new record on notification settings table
diff --git a/db/migrate/20170206115204_add_column_ghost_to_users.rb b/db/migrate/20170206115204_add_column_ghost_to_users.rb
new file mode 100644
index 00000000000..cc1eeda1160
--- /dev/null
+++ b/db/migrate/20170206115204_add_column_ghost_to_users.rb
@@ -0,0 +1,11 @@
+class AddColumnGhostToUsers < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ add_column :users, :ghost, :boolean
+ end
+
+ def down
+ remove_column :users, :ghost
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 82bccda580a..1d94368f66e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1278,10 +1278,11 @@ ActiveRecord::Schema.define(version: 20170216141440) do
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false
- t.string "organization"
t.string "incoming_email_token"
+ t.string "organization"
t.boolean "authorized_projects_populated"
t.boolean "notified_of_own_activity", default: false, null: false
+ t.boolean "ghost"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index a6300e18dc0..28e413ef447 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -466,6 +466,46 @@ If Registry is enabled in your GitLab instance, but you don't need it for your
project, you can disable it from your project's settings. Read the user guide
on how to achieve that.
+## Disable Container Registry but use GitLab as an auth endpoint
+
+You can disable the embedded Container Registry to use an external one, but
+still use GitLab as an auth endpoint.
+
+**Omnibus GitLab**
+1. Open `/etc/gitlab/gitlab.rb` and set necessary configurations:
+
+ ```ruby
+ registry['enable'] = false
+ gitlab_rails['registry_enabled'] = true
+ gitlab_rails['registry_host'] = "registry.gitlab.example.com"
+ gitlab_rails['registry_port'] = "5005"
+ gitlab_rails['registry_api_url'] = "http://localhost:5000"
+ gitlab_rails['registry_key_path'] = "/var/opt/gitlab/gitlab-rails/certificate.key"
+ gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
+ gitlab_rails['registry_issuer'] = "omnibus-gitlab-issuer"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**Installations from source**
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, and edit the configuration settings under `registry`:
+
+ ```
+ ## Container Registry
+
+ registry:
+ enabled: true
+ host: "registry.gitlab.example.com"
+ port: "5005"
+ api_url: "http://localhost:5000"
+ path: /var/opt/gitlab/gitlab-rails/shared/registry
+ key: /var/opt/gitlab/gitlab-rails/certificate.key
+ issuer: omnibus-gitlab-issuer
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
## Storage limitations
Currently, there is no storage limitation, which means a user can upload an
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
index 86ef9d167e2..edb9c911aac 100644
--- a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -13,7 +13,7 @@ To enable the GitLab monitor exporter:
1. Add or find and uncomment the following line, making sure it's set to `true`:
```ruby
- gitlab_monitor_exporter['enable'] = true
+ gitlab_monitor['enable'] = true
```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 1c444cf0d50..62b0468da79 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -26,22 +26,24 @@ it works.
---
-In the case of custom domains, the Pages daemon needs to listen on ports `80`
-and/or `443`. For that reason, there is some flexibility in the way which you
-can set it up:
+In the case of [custom domains](#custom-domains) (but not
+[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
+ports `80` and/or `443`. For that reason, there is some flexibility in the way
+which you can set it up:
-1. Run the pages daemon in the same server as GitLab, listening on a secondary IP.
-1. Run the pages daemon in a separate server. In that case, the
+1. Run the Pages daemon in the same server as GitLab, listening on a secondary IP.
+1. Run the Pages daemon in a separate server. In that case, the
[Pages path](#change-storage-path) must also be present in the server that
- the pages daemon is installed, so you will have to share it via network.
-1. Run the pages daemon in the same server as GitLab, listening on the same IP
+ the Pages daemon is installed, so you will have to share it via network.
+1. Run the Pages daemon in the same server as GitLab, listening on the same IP
but on different ports. In that case, you will have to proxy the traffic with
a loadbalancer. If you choose that route note that you should use TCP load
balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the
pages will not be able to be served with user provided certificates. For
HTTP it's OK to use HTTP or TCP load balancing.
-In this document, we will proceed assuming the first option.
+In this document, we will proceed assuming the first option. If you are not
+supporting custom domains a secondary IP is not needed.
## Prerequisites
@@ -54,6 +56,7 @@ Before proceeding with the Pages configuration, you will need to:
serve Pages under HTTPS.
1. (Optional but recommended) Enable [Shared runners](../../ci/runners/README.md)
so that your users don't have to bring their own.
+1. (Only for custom domains) Have a **secondary IP**.
### DNS configuration
@@ -150,7 +153,7 @@ that without TLS certificates.
>
URL scheme: `http://page.example.io` and `http://domain.com`
-In that case, the pages daemon is running, Nginx still proxies requests to
+In that case, the Pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains are supported, but no TLS.
@@ -179,7 +182,7 @@ world. Custom domains are supported, but no TLS.
>
URL scheme: `https://page.example.io` and `https://domain.com`
-In that case, the pages daemon is running, Nginx still proxies requests to
+In that case, the Pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains and TLS are supported.
diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md
index 00494e7e9d6..4f5c22e2d29 100644
--- a/doc/administration/reply_by_email.md
+++ b/doc/administration/reply_by_email.md
@@ -69,7 +69,9 @@ please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4).
If you want to use Gmail / Google Apps with Reply by email, make sure you have
[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018)
-and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255)
+or [turn-on 2-step validation](https://support.google.com/accounts/answer/185839)
+and use [an application password](https://support.google.com/mail/answer/185833).
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow the
[Postfix setup documentation](reply_by_email_postfix_setup.md).
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 4a39dbc5555..39adb5be502 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -14,6 +14,7 @@ Parameters:
| `order_by` | string | no | Order groups by `name` or `path`. Default is `name` |
| `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` |
| `statistics` | boolean | no | Include group statistics (admins only) |
+| `owned` | boolean | no | Limit by groups owned by the current user |
```
GET /groups
@@ -40,20 +41,6 @@ GET /groups
You can search for groups by name or path, see below.
-## List owned groups
-
-Get a list of groups which are owned by the authenticated user.
-
-```
-GET /groups/owned
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `statistics` | boolean | no | Include group statistics |
-
## List a group's projects
Get a list of projects in this group.
diff --git a/doc/api/services.md b/doc/api/services.md
index fba5da6587d..b030a425a7a 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -810,3 +810,38 @@ GET /projects/:id/services/teamcity
[jira-doc]: ../user/project/integrations/jira.md
[old-jira-api]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/api/services.md#jira
+
+
+## MockCI
+
+Mock an external CI. See [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service) for an example of a companion mock service.
+
+This service is only available when your environment is set to development.
+
+### Create/Edit MockCI service
+
+Set MockCI service for a project.
+
+```
+PUT /projects/:id/services/mock-ci
+```
+
+Parameters:
+
+- `mock_service_url` (**required**) - http://localhost:4004
+
+### Delete MockCI service
+
+Delete MockCI service for a project.
+
+```
+DELETE /projects/:id/services/mock-ci
+```
+
+### Get MockCI service settings
+
+Get MockCI service settings for a project.
+
+```
+GET /projects/:id/services/mock-ci
+```
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index e141723b580..c178e224cc5 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -41,5 +41,6 @@ changes are in V4:
- Renamed `branch_name` to `branch` on DELETE `id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936)
- Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736)
- Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384)
+- Return HTTP status code `400` for all validation errors when creating or updating a member instead of sometimes `422` error. [!9523](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9523)
+- Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505)
- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449)
-
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index dd3ba1283f8..a586b095ef5 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1018,7 +1018,7 @@ A simple example:
```yaml
job1:
- coverage: /Code coverage: \d+\.\d+/
+ coverage: '/Code coverage: \d+\.\d+/'
```
## Git Strategy
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index 2f49b3564ab..b03216fec95 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -2,11 +2,12 @@
This document describes what services we use for testing GitLab and GitLab CI.
-We currently use three CI services to test GitLab:
+We currently use four CI services to test GitLab:
1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
+4. [Mock CI Service](user/project/integrations/mock_ci.md) for local development
| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index da410a8de7a..137154e24f3 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -27,19 +27,19 @@
- **Hobbies / interests**<br>Functional programming, open source, gaming, web development and web security.
#### Motivations
-Steven works for a software development company which currently hires around 80 people. When Steven first joined the company, the engineering team were using Subversion (SVN) as their primary form of source control. However, Steven felt SVN was not flexible enough to work with many feature branches and noticed that developers with less experience of source control struggled with the central-repository nature of SVN. Armed with a wishlist of features, Steven began comparing source control tools. A search for “self-hosted Git server repository management” returned GitLab. In his own words, Steven explains why he wanted the engineering team to start using GitLab:
+Nazim works for a software development company which currently hires around 80 people. When Nazim first joined the company, the engineering team were using Subversion (SVN) as their primary form of source control. However, Nazim felt SVN was not flexible enough to work with many feature branches and noticed that developers with less experience of source control struggled with the central-repository nature of SVN. Armed with a wishlist of features, Nazim began comparing source control tools. A search for “self-hosted Git server repository management” returned GitLab. In his own words, Nazim explains why he wanted the engineering team to start using GitLab:
>
“I wanted them to switch away from SVN. I needed a server application to manage repositories. The common tools that were around just didn’t meet the requirements. Most of them were too simple or plain...GitLab provided all the required features. Also costs had to be low, since we don’t have a big budget for those things...the Community Edition was perfect in this regard.”
>
-In his role as a full-stack web developer, Steven could recommend products that he would like the engineering team to use, but final approval lay with his line manager, Mike, VP of Engineering. Steven recalls that he was met with reluctance from his colleagues when he raised moving to Git and using GitLab.
+In his role as a full-stack web developer, Nazim could recommend products that he would like the engineering team to use, but final approval lay with his line manager, Mike, VP of Engineering. Nazim recalls that he was met with reluctance from his colleagues when he raised moving to Git and using GitLab.
>
“The biggest challenge...why should we change anything at all from the status quo? We needed to switch from SVN to Git. They knew they needed to learn Git and a Git workflow...using Git was scary to my colleagues...they thought it was more complex than SVN to use.”
>
-Undeterred, Steven decided to migrate a couple of projects across to GitLab.
+Undeterred, Nazim decided to migrate a couple of projects across to GitLab.
>
“Old SVN users couldn’t see the benefits of Git at first. It took a month or two to convince them.”
@@ -47,17 +47,17 @@ Undeterred, Steven decided to migrate a couple of projects across to GitLab.
Slowly, by showing his colleagues how easy it was to use Git, the majority of the team’s projects were migrated to GitLab.
-The engineering team have been using GitLab CE for around 2 years now. Steven credits himself as being entirely responsible for his company’s decision to move to GitLab.
+The engineering team have been using GitLab CE for around 2 years now. Nazim credits himself as being entirely responsible for his company’s decision to move to GitLab.
#### Frustrations
##### Adoption to GitLab has been slow
-Not only has the engineering team had to get to grips with Git, they’ve also had to adapt to using GitLab. Due to lack of training and existing skills in other tools, the full feature set of GitLab CE is not being utilised. Steven sold GitLab to his manager as an ‘all in one’ tool which would replace multiple tools used within the company, thus saving costs. Steven hasn’t had the time to integrate the legacy tools to GitLab and he’s struggling to convince his peers to change their habits.
+Not only has the engineering team had to get to grips with Git, they’ve also had to adapt to using GitLab. Due to lack of training and existing skills in other tools, the full feature set of GitLab CE is not being utilised. Nazim sold GitLab to his manager as an ‘all in one’ tool which would replace multiple tools used within the company, thus saving costs. Nazim hasn’t had the time to integrate the legacy tools to GitLab and he’s struggling to convince his peers to change their habits.
##### Missing Features
-Steven’s company want GitLab to be able to do everything. There isn’t a large budget for software, so they’re selective about what tools are implemented. It needs to add real value to the company. In order for GitLab to be widely adopted and to meet the requirements of different roles within the company, it needs a host of features. When an individual within Steven’s company wants to know if GitLab has a specific feature or does a particular thing, Steven is the person to ask. He becomes the point of contact to investigate, build or sometimes just raise the feature request. Steven gets frustrated when GitLab isn’t able to do what he or his colleagues need it to do.
+Nazim’s company want GitLab to be able to do everything. There isn’t a large budget for software, so they’re selective about what tools are implemented. It needs to add real value to the company. In order for GitLab to be widely adopted and to meet the requirements of different roles within the company, it needs a host of features. When an individual within Nazim’s company wants to know if GitLab has a specific feature or does a particular thing, Nazim is the person to ask. He becomes the point of contact to investigate, build or sometimes just raise the feature request. Nazim gets frustrated when GitLab isn’t able to do what he or his colleagues need it to do.
##### Regressions and bugs
-Steven often has to calm down his colleagues, when a release contains regressions or new bugs. As he puts it “every new version adds something awesome, but breaks something”. He feels that “old issues for "minor" annoyances get quickly buried in the mass of open issues and linger for a very long time. More generally, I have the feeling that GitLab focus on adding new functionalities, but overlook a bunch of annoying minor regressions or introduced bugs.” Due to limited resource and expertise within the team, not only is it difficult to remain up-to-date with the frequent release cycle, it’s also counterproductive to fix workflows every month.
+Nazim often has to calm down his colleagues, when a release contains regressions or new bugs. As he puts it “every new version adds something awesome, but breaks something”. He feels that “old issues for "minor" annoyances get quickly buried in the mass of open issues and linger for a very long time. More generally, I have the feeling that GitLab focus on adding new functionalities, but overlook a bunch of annoying minor regressions or introduced bugs.” Due to limited resource and expertise within the team, not only is it difficult to remain up-to-date with the frequent release cycle, it’s also counterproductive to fix workflows every month.
##### Uses too much RAM and CPU
>
@@ -65,7 +65,7 @@ Steven often has to calm down his colleagues, when a release contains regression
>
##### UI/UX
-GitLab’s interface initially attracted Steven when he was comparing version control software. He thought it would help his less technical colleagues to adapt to using Git and perhaps, GitLab could be rolled out to other areas of the business, beyond engineering. However, using GitLab’s interface daily has left him frustrated at the lack of personalisation / control over his user experience. He’s also regularly lost in a maze of navigation. Whilst he acknowledges that GitLab listens to its users and that the interface is improving, he becomes annoyed when the changes are too progressive. “Too frequent UI changes. Most of them tend to turn out great after a few cycles of fixes, but the frequency is still far too high for me to feel comfortable to always stay on the current release.”
+GitLab’s interface initially attracted Nazim when he was comparing version control software. He thought it would help his less technical colleagues to adapt to using Git and perhaps, GitLab could be rolled out to other areas of the business, beyond engineering. However, using GitLab’s interface daily has left him frustrated at the lack of personalisation / control over his user experience. He’s also regularly lost in a maze of navigation. Whilst he acknowledges that GitLab listens to its users and that the interface is improving, he becomes annoyed when the changes are too progressive. “Too frequent UI changes. Most of them tend to turn out great after a few cycles of fixes, but the frequency is still far too high for me to feel comfortable to always stay on the current release.”
#### Goals
* To convince his colleagues to fully adopt GitLab CE, thus improving workflow and collaboration.
@@ -121,8 +121,8 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w
#### Goals
* To be able to integrate third party tools easily with GitLab EE and to create custom integrations and patches where needed.
-* To use GitLab EE primarily for code hosting, merge requests, continuous integration and issue management. Steven and his team want to be able to understand and use these particular features easily.
-* To able to share one instance of GitLab EE with multiple teams across the business. Advanced user management, the ability to separate permissions on different parts of the source code, etc are important to Steven.
+* To use GitLab EE primarily for code hosting, merge requests, continuous integration and issue management. James and his team want to be able to understand and use these particular features easily.
+* To able to share one instance of GitLab EE with multiple teams across the business. Advanced user management, the ability to separate permissions on different parts of the source code, etc are important to James.
<hr>
@@ -144,21 +144,21 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w
- **Hobbies / interests**<br>Web development, mobile development, UX, open source, gaming and travel.
#### Motivations
-Harry has been using GitLab.com for around a year. He roughly spends 8 hours every week programming, of that, 2 hours is spent contributing to open source projects. Harry contributes to open source projects to gain programming experience and to give back to the community. He likes GitLab.com for its free private repositories and range of features which provide him with everything he needs for his personal projects. Harry is also a massive fan of GitLab’s values and the fact that it isn’t a “behemoth of a company”. He explains that “displaying every single thing (doc, culture, assumptions, development...) in the open gives me greater confidence to choose Gitlab personally and to recommend it at work.” He’s also an avid reader of GitLab’s blog.
+Karolina has been using GitLab.com for around a year. She roughly spends 8 hours every week programming, of that, 2 hours is spent contributing to open source projects. Karolina contributes to open source projects to gain programming experience and to give back to the community. She likes GitLab.com for its free private repositories and range of features which provide her with everything she needs for her personal projects. Karolina is also a massive fan of GitLab’s values and the fact that it isn’t a “behemoth of a company”. She explains that “displaying every single thing (doc, culture, assumptions, development...) in the open gives me greater confidence to choose Gitlab personally and to recommend it at work.” She’s also an avid reader of GitLab’s blog.
-Harry works for a software development company which currently hires around 500 people. Harry would love to use GitLab at work but the company has used GitHub Enterprise for a number of years. He describes management at his company as “old fashioned” and explains that it’s “less of a technical issue and more of a cultural issue” to convince upper management to move to GitLab. Harry is also relatively new to the company so he’s apprehensive about pushing too hard to change version control platforms.
+Karolina works for a software development company which currently hires around 500 people. Karolina would love to use GitLab at work but the company has used GitHub Enterprise for a number of years. She describes management at her company as “old fashioned” and explains that it’s “less of a technical issue and more of a cultural issue” to convince upper management to move to GitLab. Karolina is also relatively new to the company so she’s apprehensive about pushing too hard to change version control platforms.
#### Frustrations
##### Unable to use GitLab at work
-Harry wants to use GitLab at work but isn’t sure how to approach the subject with management. In his current role, he doesn’t feel that he has the authority to request GitLab.
+Karolina wants to use GitLab at work but isn’t sure how to approach the subject with management. In her current role, she doesn’t feel that she has the authority to request GitLab.
##### Performance
-GitLab.com is frequently slow and unavailable. Harry has also heard that GitLab is a “memory hog” which has deterred him from running GitLab on his own machine for just hobby / personal projects.
+GitLab.com is frequently slow and unavailable. Karolina has also heard that GitLab is a “memory hog” which has deterred her from running GitLab on her own machine for just hobby / personal projects.
##### UX/UI
-Harry has an interest in UX and therefore has strong opinions about how GitLab should look and feel. He feels the interface is cluttered, “it has too many links/buttons” and the navigation “feels a bit weird sometimes. I get lost if I don’t pay attention.” As Harry also enjoys contributing to open-source projects, it’s important to him that GitLab is well designed for public repositories, he doesn’t feel that GitLab currently achieves this.
+Karolina has an interest in UX and therefore has strong opinions about how GitLab should look and feel. She feels the interface is cluttered, “it has too many links/buttons” and the navigation “feels a bit weird sometimes. I get lost if I don’t pay attention.” As Karolina also enjoys contributing to open-source projects, it’s important to her that GitLab is well designed for public repositories, she doesn’t feel that GitLab currently achieves this.
#### Goals
-* To develop his programming experience and to learn from other developers.
-* To contribute to both his own and other open source projects.
+* To develop her programming experience and to learn from other developers.
+* To contribute to both her own and other open source projects.
* To use a fast and intuitive version control platform. \ No newline at end of file
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index 212b4854dd7..c39d7ab57c6 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -54,7 +54,7 @@ for initial settings.
gitlab_rails['omniauth_providers'] = [
{
"name" => "auth0",
- "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID'',
+ "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID',
client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
namespace: 'YOUR_AUTH0_DOMAIN'
}
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 64df7ee48bd..eb6f915f3f4 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -16,8 +16,6 @@ that this setting is set for each job.
1. Hit **Save** for the changes to take effect.
-[art-yml]: ../../../administration/build_artifacts
-
## Default artifacts expiration
The default expiration time of the [job artifacts][art-yml] can be set in
@@ -36,5 +34,5 @@ expiration.
1. Hit **Save** for the changes to take effect.
-[art-yml]: ../../../administration/job_artifacts
-[duration-syntax]: ../../../ci/yaml/README#artifactsexpire_in
+[art-yml]: ../../../administration/job_artifacts.md
+[duration-syntax]: ../../../ci/yaml/README.md#artifactsexpire_in
diff --git a/doc/user/project/integrations/img/mattermost_configuration.png b/doc/user/project/integrations/img/mattermost_configuration.png
index 3c5ff5ee317..f52acf4ef3b 100644
--- a/doc/user/project/integrations/img/mattermost_configuration.png
+++ b/doc/user/project/integrations/img/mattermost_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/slack_configuration.png b/doc/user/project/integrations/img/slack_configuration.png
index fc8e58e686b..527824fc3eb 100644
--- a/doc/user/project/integrations/img/slack_configuration.png
+++ b/doc/user/project/integrations/img/slack_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index 09ba9994d3a..cfb0931273d 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -24,23 +24,24 @@ There, you will see a checkbox with the following events that can be triggered:
- Push
- Issue
+- Confidential issue
- Merge request
- Note
- Tag push
- Build
+- Pipeline
- Wiki page
-Bellow each of these event checkboxes, you will have an input field to insert
-which Mattermost channel you want to send that event message, with `#town-square`
-being the default. The hash sign is optional.
+Below each of these event checkboxes, you have an input field to enter
+which Mattermost channel you want to send that event message. Enter your preferred channel handle (the hash sign `#` is optional).
At the end, fill in your Mattermost details:
| Field | Description |
| ----- | ----------- |
-| **Webhook** | The incoming webhooks which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo... |
+| **Webhook** | The incoming webhook URL which you have to setup on Mattermost, it will be something like: http://mattermost.example/hooks/5xo… |
| **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. |
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
-
+| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. |
![Mattermost configuration](img/mattermost_configuration.png)
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
new file mode 100644
index 00000000000..6aefe5dbded
--- /dev/null
+++ b/doc/user/project/integrations/mock_ci.md
@@ -0,0 +1,13 @@
+# Mock CI Service
+
+**NB: This service is only listed if you are in a development environment!**
+
+To setup the mock CI service server, respond to the following endpoints
+
+- `commit_status`: `#{project.namespace.path}/#{project.path}/status/#{sha}.json`
+ - Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success_with_warnings'|'skipped'|'not_found'] }`
+ - If the service returns a 404, it is interpreted as `pending`
+- `build_page`: `#{project.namespace.path}/#{project.path}/status/#{sha}`
+ - Just where the build is linked to, doesn't matter if implemented
+
+For an example of a mock CI server, see [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service)
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 57a9492044b..f27f9a726fc 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -21,23 +21,25 @@ There, you will see a checkbox with the following events that can be triggered:
- Push
- Issue
+- Confidential issue
- Merge request
- Note
- Tag push
- Build
+- Pipeline
- Wiki page
-Bellow each of these event checkboxes, you will have an input field to insert
-which Slack channel you want to send that event message, with `#general`
-being the default. Enter your preferred channel **without** the hash sign (`#`).
+Below each of these event checkboxes, you have an input field to enter
+which Slack channel you want to send that event message. Enter your preferred channel name **without** the hash sign (`#`).
At the end, fill in your Slack details:
| Field | Description |
| ----- | ----------- |
| **Webhook** | The [incoming webhook URL][slackhook] which you have to setup on Slack. |
-| **Username** | Optional username which can be on messages sent to slack. Fill this in if you want to change the username of the bot. |
+| **Username** | Optional username which can be on messages sent to Slack. Fill this in if you want to change the username of the bot. |
| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
+| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. |
After you are all done, click **Save changes** for the changes to take effect.
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 8c5020bee37..9cc45065eb2 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -63,6 +63,12 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
+>**Note**: Make sure that `.gitattributes` is tracked by git. Otherwise Git
+ LFS will not be working properly for people cloning the project.
+ ```bash
+ git add .gitattributes
+ ```
+
Cloning the repository works the same as before. Git automatically detects the
LFS-tracked files and clones them via HTTP. If you performed the git clone
command with a SSH URL, you have to enter your GitLab credentials for HTTP
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index 88fef674c0c..c57376aecff 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -13,6 +13,7 @@ Feature: Project Commits Branches
Given I visit project protected branches page
Then I should see "Shop" protected branches list
+ @javascript
Scenario: I create a branch
Given I visit project branches page
And I click new branch link
@@ -33,12 +34,7 @@ Feature: Project Commits Branches
And I submit new branch form with invalid name
Then I should see new an error that branch is invalid
- Scenario: I create a branch with invalid reference
- Given I visit project branches page
- And I click new branch link
- And I submit new branch form with invalid reference
- Then I should see new an error that ref is invalid
-
+ @javascript
Scenario: I create a branch that already exists
Given I visit project branches page
And I click new branch link
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 2b4a5ab0864..7dc33ab5683 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -49,7 +49,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
step 'I should see an http link to the repository' do
project = Project.find_by(name: 'Community')
- expect(page).to have_field('project_clone', with: project.http_url_to_repo)
+ expect(page).to have_field('project_clone', with: project.http_url_to_repo(@user))
end
step 'I should see an ssh link to the repository' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 5f9b9e0445e..ccaf3237815 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -34,25 +34,19 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step 'I submit new branch form' do
fill_in 'branch_name', with: 'deploy_keys'
- fill_in 'ref', with: 'master'
+ select_branch('master')
click_button 'Create branch'
end
step 'I submit new branch form with invalid name' do
fill_in 'branch_name', with: '1.0 stable'
- fill_in 'ref', with: 'master'
- click_button 'Create branch'
- end
-
- step 'I submit new branch form with invalid reference' do
- fill_in 'branch_name', with: 'foo'
- fill_in 'ref', with: 'foo'
+ select_branch('master')
click_button 'Create branch'
end
step 'I submit new branch form with branch that already exists' do
fill_in 'branch_name', with: 'master'
- fill_in 'ref', with: 'master'
+ select_branch('master')
click_button 'Create branch'
end
@@ -65,10 +59,6 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
expect(page).to have_content "can't contain spaces"
end
- step 'I should see new an error that ref is invalid' do
- expect(page).to have_content 'Invalid reference name'
- end
-
step 'I should see new an error that branch already exists' do
expect(page).to have_content 'Branch already exists'
end
@@ -88,4 +78,12 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step "I should not see branch 'improve/awesome'" do
expect(page.all(visible: true)).not_to have_content 'improve/awesome'
end
+
+ def select_branch(branch_name)
+ click_button 'master'
+
+ page.within '#new-branch-form .dropdown-menu' do
+ click_link branch_name
+ end
+ end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 1803387bb8c..7aa95a4a3c1 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -10,6 +10,7 @@ module API
mount ::API::V3::Commits
mount ::API::V3::DeployKeys
mount ::API::V3::Files
+ mount ::API::V3::Groups
mount ::API::V3::Issues
mount ::API::V3::Labels
mount ::API::V3::Members
@@ -90,6 +91,7 @@ module API
mount ::API::Projects
mount ::API::ProjectSnippets
mount ::API::Repositories
+ mount ::API::Runner
mount ::API::Runners
mount ::API::Services
mount ::API::Session
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 9ce396d4660..fd03e92264d 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -52,13 +52,6 @@ module API
attrs = declared_params.merge(start_branch: declared_params[:branch], target_branch: declared_params[:branch])
- attrs[:actions].map! do |action|
- action[:action] = action[:action].to_sym
- action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
- action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
- action
- end
-
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 0e37f40a887..a99d9cadc8a 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -618,6 +618,10 @@ module API
end
end
+ class RunnerRegistrationDetails < Grape::Entity
+ expose :id, :token
+ end
+
class BuildArtifactFile < Grape::Entity
expose :filename, :size
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9f29c4466ab..9cffd6180ae 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -36,12 +36,15 @@ module API
optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
optional :search, type: String, desc: 'Search for a specific group'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path'
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get do
- groups = if current_user.admin
+ groups = if params[:owned]
+ current_user.owned_groups
+ elsif current_user.admin
Group.all
elsif params[:all_available]
GroupsFinder.new.execute(current_user)
@@ -56,17 +59,6 @@ module API
present_groups groups, statistics: params[:statistics] && current_user.is_admin?
end
- desc 'Get list of owned groups for authenticated user' do
- success Entities::Group
- end
- params do
- use :pagination
- use :statistics_params
- end
- get '/owned' do
- present_groups current_user.owned_groups, statistics: params[:statistics]
- end
-
desc 'Create a group. Available only for users who can create groups.' do
success Entities::Group
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
new file mode 100644
index 00000000000..119ca81b883
--- /dev/null
+++ b/lib/api/helpers/runner.rb
@@ -0,0 +1,23 @@
+module API
+ module Helpers
+ module Runner
+ def runner_registration_token_valid?
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
+ current_application_settings.runners_registration_token)
+ end
+
+ def get_runner_version_from_params
+ return unless params['info'].present?
+ attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ end
+
+ def authenticate_runner!
+ forbidden! unless current_runner
+ end
+
+ def current_runner
+ @runner ||= ::Ci::Runner.find_by_token(params[:token].to_s)
+ end
+ end
+ end
+end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 8360c007005..5f6913d1a27 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -55,7 +55,6 @@ module API
authorize_admin_source!(source_type, source)
member = source.members.find_by(user_id: params[:user_id])
-
conflict!('Member already exists') if member
member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at])
@@ -63,9 +62,6 @@ module API
if member.persisted? && member.valid?
present member.user, with: Entities::Member, member: member
else
- # This is to ensure back-compatibility but 400 behavior should be used
- # for all validation errors in 9.0!
- render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
@@ -87,9 +83,6 @@ module API
if member.update_attributes(declared_params(include_missing: false))
present member.user, with: Entities::Member, member: member
else
- # This is to ensure back-compatibility but 400 behavior should be used
- # for all validation errors in 9.0!
- render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
render_validation_error!(member)
end
end
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
new file mode 100644
index 00000000000..804b27d40a7
--- /dev/null
+++ b/lib/api/runner.rb
@@ -0,0 +1,52 @@
+module API
+ class Runner < Grape::API
+ helpers ::API::Helpers::Runner
+
+ resource :runners do
+ desc 'Registers a new Runner' do
+ success Entities::RunnerRegistrationDetails
+ http_codes [[201, 'Runner was created'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: 'Registration token'
+ optional :description, type: String, desc: %q(Runner's description)
+ optional :info, type: Hash, desc: %q(Runner's metadata)
+ optional :locked, type: Boolean, desc: 'Should Runner be locked for current project'
+ optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs'
+ optional :tag_list, type: Array[String], desc: %q(List of Runner's tags)
+ end
+ post '/' do
+ attributes = attributes_for_keys [:description, :locked, :run_untagged, :tag_list]
+
+ runner =
+ if runner_registration_token_valid?
+ # Create shared runner. Requires admin access
+ Ci::Runner.create(attributes.merge(is_shared: true))
+ elsif project = Project.find_by(runners_token: params[:token])
+ # Create a specific runner for project.
+ project.runners.create(attributes)
+ end
+
+ return forbidden! unless runner
+
+ if runner.id
+ runner.update(get_runner_version_from_params)
+ present runner, with: Entities::RunnerRegistrationDetails
+ else
+ not_found!
+ end
+ end
+
+ desc 'Deletes a registered Runner' do
+ http_codes [[200, 'Runner was deleted'], [403, 'Forbidden']]
+ end
+ params do
+ requires :token, type: String, desc: %q(Runner's authentication token)
+ end
+ delete '/' do
+ authenticate_runner!
+ Ci::Runner.find_by_token(params[:token]).destroy
+ end
+ end
+ end
+end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 1456fe4688b..ad856115485 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -563,7 +563,20 @@ module API
SlackService,
MattermostService,
TeamcityService,
- ].freeze
+ ]
+
+ if Rails.env.development?
+ services['mock-ci'] = [
+ {
+ required: true,
+ name: :mock_service_url,
+ type: String,
+ desc: 'URL to the mock service'
+ }
+ ]
+
+ service_classes << MockCiService
+ end
trigger_services = {
'mattermost-slash-commands' => [
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 87a717ba751..ea0ad852633 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -21,14 +21,9 @@ module API
unauthorized! unless trigger.project == project
# validate variables
- variables = params[:variables]
- if variables
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
- render_api_error!('variables needs to be a map of key-valued strings', 400)
- end
-
- # convert variables from Mash to Hash
- variables = variables.to_h
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index 126cc95fc3d..506204b3517 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -55,13 +55,6 @@ module API
branch = attrs.delete(:branch_name)
attrs.merge!(branch: branch, start_branch: branch, target_branch: branch)
- attrs[:actions].map! do |action|
- action[:action] = action[:action].to_sym
- action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
- action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
- action
- end
-
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
new file mode 100644
index 00000000000..c826bc4fe0b
--- /dev/null
+++ b/lib/api/v3/groups.rb
@@ -0,0 +1,38 @@
+module API
+ module V3
+ class Groups < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ helpers do
+ params :statistics_params do
+ optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
+ end
+
+ def present_groups(groups, options = {})
+ options = options.reverse_merge(
+ with: ::API::Entities::Group,
+ current_user: current_user,
+ )
+
+ groups = groups.with_statistics if options[:statistics]
+ present paginate(groups), options
+ end
+ end
+
+ resource :groups do
+ desc 'Get list of owned groups for authenticated user' do
+ success ::API::Entities::Group
+ end
+ params do
+ use :pagination
+ use :statistics_params
+ end
+ get '/owned' do
+ present_groups current_user.owned_groups, statistics: params[:statistics]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index f0fb6084a35..651b55523c0 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -8,11 +8,6 @@ module Banzai
# of the anchor, and then replace the img with the link-wrapped version.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
- div = doc.document.create_element(
- 'div',
- class: 'image-container'
- )
-
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
@@ -22,9 +17,7 @@ module Banzai
link.children = img.clone
- div.children = link
-
- img.replace(div)
+ img.replace(link)
end
doc
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index b742d9e1e4b..e56eb0d3beb 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -93,163 +93,6 @@ module Gitlab
commit_id: sha,
)
end
-
- # Commit file in repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # content: 'Lorem ipsum...',
- # path: 'documents/story.txt',
- # update: true
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Wow such commit',
- # branch: 'master',
- # update_ref: false
- # }
- #
- # rubocop:disable Metrics/AbcSize
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/PerceivedComplexity
- def commit(repository, options, action = :add)
- file = options[:file]
- update = file[:update].nil? ? true : file[:update]
- author = options[:author]
- committer = options[:committer]
- commit = options[:commit]
- repo = repository.rugged
- ref = commit[:branch]
- update_ref = commit[:update_ref].nil? ? true : commit[:update_ref]
- parents = []
- mode = 0o100644
-
- unless ref.start_with?('refs/')
- ref = 'refs/heads/' + ref
- end
-
- path_name = Gitlab::Git::PathHelper.normalize_path(file[:path])
- # Abort if any invalid characters remain (e.g. ../foo)
- raise Gitlab::Git::Repository::InvalidBlobName.new("Invalid path") if path_name.each_filename.to_a.include?('..')
-
- filename = path_name.to_s
- index = repo.index
-
- unless repo.empty?
- rugged_ref = repo.references[ref]
- raise Gitlab::Git::Repository::InvalidRef.new("Invalid branch name") unless rugged_ref
- last_commit = rugged_ref.target
- index.read_tree(last_commit.tree)
- parents = [last_commit]
- end
-
- if action == :remove
- index.remove(filename)
- else
- file_entry = index.get(filename)
-
- if action == :rename
- old_path_name = Gitlab::Git::PathHelper.normalize_path(file[:previous_path])
- old_filename = old_path_name.to_s
- file_entry = index.get(old_filename)
- index.remove(old_filename) unless file_entry.blank?
- end
-
- if file_entry
- raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists; update not allowed") unless update
-
- # Preserve the current file mode if one is available
- mode = file_entry[:mode] if file_entry[:mode]
- end
-
- content = file[:content]
- detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
-
- unless detect && detect[:type] == :binary
- # When writing to the repo directly as we are doing here,
- # the `core.autocrlf` config isn't taken into account.
- content.gsub!("\r\n", "\n") if repository.autocrlf
- end
-
- oid = repo.write(content, :blob)
- index.add(path: filename, oid: oid, mode: mode)
- end
-
- opts = {}
- opts[:tree] = index.write_tree(repo)
- opts[:author] = author
- opts[:committer] = committer
- opts[:message] = commit[:message]
- opts[:parents] = parents
- opts[:update_ref] = ref if update_ref
-
- Rugged::Commit.create(repo, opts)
- end
- # rubocop:enable Metrics/AbcSize
- # rubocop:enable Metrics/CyclomaticComplexity
- # rubocop:enable Metrics/PerceivedComplexity
-
- # Remove file from repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # path: 'documents/story.txt'
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Remove FILENAME',
- # branch: 'master'
- # }
- #
- def remove(repository, options)
- commit(repository, options, :remove)
- end
-
- # Rename file from repository and return commit sha
- #
- # options should contain next structure:
- # file: {
- # previous_path: 'documents/old_story.txt'
- # path: 'documents/story.txt'
- # content: 'Lorem ipsum...',
- # update: true
- # },
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Rename FILENAME',
- # branch: 'master'
- # }
- #
- def rename(repository, options)
- commit(repository, options, :rename)
- end
end
def initialize(options)
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
new file mode 100644
index 00000000000..af1744c9c46
--- /dev/null
+++ b/lib/gitlab/git/index.rb
@@ -0,0 +1,126 @@
+module Gitlab
+ module Git
+ class Index
+ DEFAULT_MODE = 0o100644
+
+ attr_reader :repository, :raw_index
+
+ def initialize(repository)
+ @repository = repository
+ @raw_index = repository.rugged.index
+ end
+
+ delegate :read_tree, :get, to: :raw_index
+
+ def write_tree
+ raw_index.write_tree(repository.rugged)
+ end
+
+ def dir_exists?(path)
+ raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
+ end
+
+ def create(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ if file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists")
+ end
+
+ add_blob(options)
+ end
+
+ def create_dir(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ if file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists as a file")
+ end
+
+ if dir_exists?(options[:file_path])
+ raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists")
+ end
+
+ options = options.dup
+ options[:file_path] += '/.gitkeep'
+ options[:content] = ''
+
+ add_blob(options)
+ end
+
+ def update(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ add_blob(options, mode: file_entry[:mode])
+ end
+
+ def move(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:previous_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ raw_index.remove(options[:previous_path])
+
+ add_blob(options, mode: file_entry[:mode])
+ end
+
+ def delete(options)
+ options = normalize_options(options)
+
+ file_entry = get(options[:file_path])
+ unless file_entry
+ raise Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
+ end
+
+ raw_index.remove(options[:file_path])
+ end
+
+ private
+
+ def normalize_options(options)
+ options = options.dup
+ options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
+ options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
+ options
+ end
+
+ def normalize_path(path)
+ pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)
+
+ if pathname.each_filename.include?('..')
+ raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
+ end
+
+ pathname.to_s
+ end
+
+ def add_blob(options, mode: nil)
+ content = options[:content]
+ content = Base64.decode64(content) if options[:encoding] == 'base64'
+
+ detect = CharlockHolmes::EncodingDetector.new.detect(content)
+ unless detect && detect[:type] == :binary
+ # When writing to the repo directly as we are doing here,
+ # the `core.autocrlf` config isn't taken into account.
+ content.gsub!("\r\n", "\n") if repository.autocrlf
+ end
+
+ oid = repository.rugged.write(content, :blob)
+
+ raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
+ rescue Rugged::IndexError => e
+ raise Gitlab::Git::Repository::InvalidBlobName.new(e.message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 4b6ad8037ce..8ec90885231 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -324,24 +324,30 @@ module Gitlab
end
def log_by_shell(sha, options)
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} log)
- cmd += %W(-n #{options[:limit].to_i})
- cmd += %w(--format=%H)
- cmd += %W(--skip=#{options[:offset].to_i})
- cmd += %w(--follow) if options[:follow]
- cmd += %w(--no-merges) if options[:skip_merges]
- cmd += %W(--after=#{options[:after].iso8601}) if options[:after]
- cmd += %W(--before=#{options[:before].iso8601}) if options[:before]
- cmd += [sha]
- cmd += %W(-- #{options[:path]}) if options[:path].present?
-
- raw_output = IO.popen(cmd) {|io| io.read }
-
- log = raw_output.lines.map do |c|
- Rugged::Commit.new(rugged, c.strip)
- end
+ limit = options[:limit].to_i
+ offset = options[:offset].to_i
+ use_follow_flag = options[:follow] && options[:path].present?
+
+ # We will perform the offset in Ruby because --follow doesn't play well with --skip.
+ # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
+ offset_in_ruby = use_follow_flag && options[:offset].present?
+ limit += offset if offset_in_ruby
+
+ cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
+ cmd << "--max-count=#{limit}"
+ cmd << '--format=%H'
+ cmd << "--skip=#{offset}" unless offset_in_ruby
+ cmd << '--follow' if use_follow_flag
+ cmd << '--no-merges' if options[:skip_merges]
+ cmd << "--after=#{options[:after].iso8601}" if options[:after]
+ cmd << "--before=#{options[:before].iso8601}" if options[:before]
+ cmd << sha
+ cmd += %W[-- #{options[:path]}] if options[:path].present?
- log.is_a?(Array) ? log : []
+ raw_output = IO.popen(cmd) { |io| io.read }
+ lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
+
+ lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
end
def sha_from_ref(ref)
@@ -837,57 +843,6 @@ module Gitlab
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end
- # Create a new directory with a .gitkeep file. Creates
- # all required nested directories (i.e. mkdir -p behavior)
- #
- # options should contain next structure:
- # author: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # committer: {
- # email: 'user@example.com',
- # name: 'Test User',
- # time: Time.now
- # },
- # commit: {
- # message: 'Wow such commit',
- # branch: 'master',
- # update_ref: false
- # }
- def mkdir(path, options = {})
- # Check if this directory exists; if it does, then don't bother
- # adding .gitkeep file.
- ref = options[:commit][:branch]
- path = Gitlab::Git::PathHelper.normalize_path(path).to_s
- rugged_ref = rugged.ref(ref)
-
- raise InvalidRef.new("Invalid ref") if rugged_ref.nil?
-
- target_commit = rugged_ref.target
-
- raise InvalidRef.new("Invalid target commit") if target_commit.nil?
-
- entry = tree_entry(target_commit, path)
-
- if entry
- if entry[:type] == :blob
- raise InvalidBlobName.new("Directory already exists as a file")
- else
- raise InvalidBlobName.new("Directory already exists")
- end
- end
-
- options[:file] = {
- content: '',
- path: "#{path}/.gitkeep",
- update: true
- }
-
- Gitlab::Git::Blob.commit(self, options)
- end
-
# Returns result like "git ls-files" , recursive and full file path
#
# Ex.
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index f1d1af8eee5..8b327cfc226 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def export_filename(project:)
- basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.full_path}_#{project.path}"
+ basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.full_path.tr('/', '_')}"
"#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
end
diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/middleware/webpack_proxy.rb
index 3fe32adeade..6105d165810 100644
--- a/lib/gitlab/middleware/webpack_proxy.rb
+++ b/lib/gitlab/middleware/webpack_proxy.rb
@@ -8,16 +8,16 @@ module Gitlab
@proxy_host = opts.fetch(:proxy_host, 'localhost')
@proxy_port = opts.fetch(:proxy_port, 3808)
@proxy_path = opts[:proxy_path] if opts[:proxy_path]
- super(app, opts)
+
+ super(app, backend: "http://#{@proxy_host}:#{@proxy_port}", **opts)
end
def perform_request(env)
- unless @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
- return @app.call(env)
+ if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
+ super(env)
+ else
+ @app.call(env)
end
-
- env['HTTP_HOST'] = "#{@proxy_host}:#{@proxy_port}"
- super(env)
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index c77fe2d8bdc..5e5f5ff1589 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -5,17 +5,18 @@ module Gitlab
# The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
# Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
- # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of
+ # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_JS` serves as a Javascript-compatible version of
# `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
- NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
- NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze
- PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
+ NO_SUFFIX_REGEX_STR = '(?<!\.git|\.atom)'.freeze
+ NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_JS})#{NO_SUFFIX_REGEX_STR}".freeze
+ PROJECT_REGEX_STR = "(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX_STR}".freeze
# Same as NAMESPACE_REGEX_STR but allows `/` in the path.
# So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
- NAMESPACE_REF_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.\/]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
+ FULL_NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR}/)*#{NAMESPACE_REGEX_STR}".freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
diff --git a/package.json b/package.json
index 66aa7e9fe5d..5528303ab21 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"stats-webpack-plugin": "^0.4.3",
"timeago.js": "^2.0.5",
"underscore": "^1.8.3",
- "vue": "^2.0.3",
+ "vue": "^2.1.10",
"vue-resource": "^0.9.3",
"webpack": "^2.2.1",
"webpack-bundle-analyzer": "^2.3.0"
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
index 2fcb4a6a528..44e011fd3a8 100644
--- a/spec/controllers/blob_controller_spec.rb
+++ b/spec/controllers/blob_controller_spec.rb
@@ -19,8 +19,8 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
@@ -50,8 +50,8 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
controller.instance_variable_set(:@blob, nil)
end
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index addc5e7ec33..c086b386381 100644
--- a/spec/controllers/projects/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -16,8 +16,8 @@ describe Projects::BlameController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 7d4636e98d1..ec36a64b415 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -14,8 +14,8 @@ describe Projects::BlobController do
render_views
def do_get(opts = {})
- params = { namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ params = { namespace_id: project.namespace,
+ project_id: project,
id: 'master/CHANGELOG' }
get :diff, params.merge(opts)
end
@@ -40,8 +40,8 @@ describe Projects::BlobController do
describe 'PUT update' do
let(:default_params) do
{
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: 'master/CHANGELOG',
target_branch: 'master',
content: 'Added changes',
@@ -96,8 +96,8 @@ describe Projects::BlobController do
context 'when editing on the fork' do
before do
- default_params[:namespace_id] = forked_project.namespace.to_param
- default_params[:project_id] = forked_project.to_param
+ default_params[:namespace_id] = forked_project.namespace
+ default_params[:project_id] = forked_project
end
it 'redirects to blob' do
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb
index ad15e3942a5..3d0533cb516 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/projects/boards/issues_controller_spec.rb
@@ -90,7 +90,7 @@ describe Projects::Boards::IssuesController do
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list_id: list.try(:to_param)
}
@@ -146,7 +146,7 @@ describe Projects::Boards::IssuesController do
sign_in(user)
post :create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list_id: list.to_param,
issue: { title: title },
@@ -209,7 +209,7 @@ describe Projects::Boards::IssuesController do
sign_in(user)
patch :update, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: issue.to_param,
from_list_id: from_list_id,
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index b3f9f76a50c..432f3c53c90 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -47,7 +47,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
get :index, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
format: :json
end
@@ -104,7 +104,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
post :create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
list: { label_id: label_id },
format: :json
@@ -157,7 +157,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
patch :update, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: list.to_param,
list: { position: position },
@@ -200,7 +200,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
delete :destroy, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
id: list.to_param,
format: :json
@@ -244,7 +244,7 @@ describe Projects::Boards::ListsController do
sign_in(user)
post :generate, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
board_id: board.to_param,
format: :json
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index cc19035740e..aed3a45c413 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -50,8 +50,8 @@ describe Projects::BoardsController do
end
def list_boards(format: :html)
- get :index, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ get :index, namespace_id: project.namespace,
+ project_id: project,
format: format
end
end
@@ -100,8 +100,8 @@ describe Projects::BoardsController do
end
def read_board(board:, format: :html)
- get :show, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ get :show, namespace_id: project.namespace,
+ project_id: project,
id: board.to_param,
format: format
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index effd8bcd982..e70737376af 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -22,8 +22,8 @@ describe Projects::BranchesController do
sign_in(user)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
ref: ref
end
@@ -76,8 +76,8 @@ describe Projects::BranchesController do
it 'redirects' do
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
@@ -89,8 +89,8 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
end
@@ -143,8 +143,8 @@ describe Projects::BranchesController do
expect(SystemNoteService).not_to receive(:new_issue_branch)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
branch_name: branch,
issue_iid: issue.iid
end
@@ -163,8 +163,8 @@ describe Projects::BranchesController do
post :destroy,
format: :html,
id: 'foo/bar/baz',
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
expect(response).to have_http_status(303)
end
@@ -179,8 +179,8 @@ describe Projects::BranchesController do
post :destroy,
format: :js,
id: branch,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context "valid branch name, valid source" do
@@ -210,8 +210,8 @@ describe Projects::BranchesController do
describe "DELETE destroy_all_merged" do
def destroy_all_merged
delete :destroy_all_merged,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when user is allowed to push' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index ebd2d0e092b..640baa3a01c 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -17,8 +17,8 @@ describe Projects::CommitController do
def go(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :show, params.merge(extra_params)
@@ -125,8 +125,8 @@ describe Projects::CommitController do
it 'renders it' do
get(:show,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
+ namespace_id: fork_project.namespace,
+ project_id: fork_project,
id: commit.id)
expect(response).to be_success
@@ -139,8 +139,8 @@ describe Projects::CommitController do
commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
get(:branches,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: commit.id)
expect(assigns(:branches)).to include("master", "feature_conflict")
@@ -152,8 +152,8 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: commit.id)
expect(response).not_to be_success
@@ -164,8 +164,8 @@ describe Projects::CommitController do
context 'when the revert was successful' do
it 'redirects to the commits page' do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
@@ -177,8 +177,8 @@ describe Projects::CommitController do
context 'when the revert failed' do
before do
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
end
@@ -186,8 +186,8 @@ describe Projects::CommitController do
it 'redirects to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: commit.id)
@@ -201,8 +201,8 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: master_pickable_commit.id)
expect(response).not_to be_success
@@ -213,8 +213,8 @@ describe Projects::CommitController do
context 'when the cherry-pick was successful' do
it 'redirects to the commits page' do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
@@ -226,8 +226,8 @@ describe Projects::CommitController do
context 'when the cherry_pick failed' do
before do
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
end
@@ -235,8 +235,8 @@ describe Projects::CommitController do
it 'redirects to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
target_branch: 'master',
id: master_pickable_commit.id)
@@ -249,8 +249,8 @@ describe Projects::CommitController do
describe 'GET diff_for_path' do
def diff_for_path(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
@@ -313,8 +313,8 @@ describe Projects::CommitController do
describe 'GET pipelines' do
def get_pipelines(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :pipelines, params.merge(extra_params)
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 54b8d1108a5..e26731fb691 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -16,8 +16,8 @@ describe Projects::CommitsController do
context "when the ref does not exist with the suffix" do
it "renders as atom" do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: "master.atom")
expect(response).to be_success
@@ -33,8 +33,8 @@ describe Projects::CommitsController do
allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: "master.atom")
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index e811c76fb31..15ac4e0925a 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -13,8 +13,8 @@ describe Projects::CompareController do
it 'compare shows some diffs' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: ref_from,
to: ref_to)
@@ -25,8 +25,8 @@ describe Projects::CompareController do
it 'compare shows some diffs with ignore whitespace change option' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '08f22f25',
to: '66eceea0',
w: 1)
@@ -43,8 +43,8 @@ describe Projects::CompareController do
describe 'non-existent refs' do
it 'uses invalid source ref' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: 'non-existent',
to: ref_to)
@@ -55,8 +55,8 @@ describe Projects::CompareController do
it 'uses invalid target ref' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: ref_from,
to: 'non-existent')
@@ -67,8 +67,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:from] is empty and preserves params[:to]' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '',
to: 'master')
@@ -77,8 +77,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: 'master',
to: '')
@@ -87,8 +87,8 @@ describe Projects::CompareController do
it 'redirects back to index when params[:from] and params[:to] are empty' do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
from: '',
to: '')
@@ -99,8 +99,8 @@ describe Projects::CompareController do
describe 'GET diff_for_path' do
def diff_for_path(extra_params = {})
params = {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 6a6d71a16ee..6fae52edbad 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -13,8 +13,8 @@ describe Projects::CycleAnalyticsController do
context 'with no data' do
it 'is true' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ namespace_id: project.namespace,
+ project_id: project)
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(true)
@@ -32,8 +32,8 @@ describe Projects::CycleAnalyticsController do
it 'is false' do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ namespace_id: project.namespace,
+ project_id: project)
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(false)
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index a4884256c92..6a5433bcc9c 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -17,8 +17,8 @@ describe Projects::FindFileController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id)
end
@@ -36,8 +36,8 @@ describe Projects::FindFileController do
describe "GET #list" do
def go(format: 'json')
get :list,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
id: id,
format: format
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index a867668d97b..8282d79298f 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -9,8 +9,8 @@ describe Projects::ForksController do
describe 'GET index' do
def get_forks
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when fork is public' do
@@ -71,8 +71,8 @@ describe Projects::ForksController do
describe 'GET new' do
def get_new
get :new,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ namespace_id: project.namespace,
+ project_id: project
end
context 'when user is signed in' do
@@ -99,8 +99,8 @@ describe Projects::ForksController do
describe 'POST create' do
def post_create
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ namespace_id: project.namespace,
+ project_id: project,
namespace_key: user.namespace.id
end
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index bbe8e4bf6b2..c4a7aa7d63e 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -34,7 +34,7 @@ describe Projects::GraphsController do
end
it 'sets the correct colour according to language' do
- get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master')
+ get(:languages, namespace_id: project.namespace, project_id: project, id: 'master')
expected_values.each do |val|
expect(assigns(:languages)).to include(a_hash_including(val))
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index a976a9c27ab..ca4a8e871c0 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -14,8 +14,8 @@ describe Projects::GroupLinksController do
describe '#create' do
shared_context 'link project to group' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_id: group.id,
link_group_access: ProjectGroupLink.default_access)
end
@@ -50,8 +50,8 @@ describe Projects::GroupLinksController do
context 'when project group id equal link group id' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_id: group2.id,
link_group_access: ProjectGroupLink.default_access)
end
@@ -69,8 +69,8 @@ describe Projects::GroupLinksController do
context 'when link group id is not present' do
before do
- post(:create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ post(:create, namespace_id: project.namespace,
+ project_id: project,
link_group_access: ProjectGroupLink.default_access)
end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 2acbba469e3..7c75815f3c4 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -13,13 +13,13 @@ describe Projects::ImportsController do
end
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'Started' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' }
expect(flash.now[:notice]).to eq 'Started'
end
@@ -39,13 +39,13 @@ describe Projects::ImportsController do
end
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'In progress' }
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' }
expect(flash.now[:notice]).to eq 'In progress'
end
@@ -57,7 +57,7 @@ describe Projects::ImportsController do
end
it 'redirects to new_namespace_project_import_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
end
@@ -72,7 +72,7 @@ describe Projects::ImportsController do
it 'redirects to namespace_project_path' do
allow_any_instance_of(Project).to receive(:forked?).and_return(true)
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully forked.'
expect(response).to redirect_to namespace_project_path(project.namespace, project)
@@ -81,7 +81,7 @@ describe Projects::ImportsController do
context 'when project is external' do
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully imported.'
expect(response).to redirect_to namespace_project_path(project.namespace, project)
@@ -97,7 +97,7 @@ describe Projects::ImportsController do
end
it 'redirects to params[:to]' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: params
+ get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
expect(flash[:notice]).to eq params[:notice]
expect(response).to redirect_to params[:to]
@@ -111,7 +111,7 @@ describe Projects::ImportsController do
end
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :show, namespace_id: project.namespace.to_param, project_id: project
expect(response).to redirect_to namespace_project_path(project.namespace, project)
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 7871b6a9e10..46c758b4654 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::IssuesController do
allow(project).to receive(:external_issue_tracker).and_return(external)
controller.instance_variable_set(:@project, project)
- get :index, namespace_id: project.namespace.path, project_id: project
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to redirect_to('https://example.com/project')
end
@@ -27,13 +27,13 @@ describe Projects::IssuesController do
it_behaves_like "issuables list meta-data", :issue
it "returns index" do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(200)
end
it "returns 301 if request path doesn't match project path" do
- get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+ get :index, namespace_id: project.namespace, project_id: project.path.upcase
expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
end
@@ -42,7 +42,7 @@ describe Projects::IssuesController do
project.issues_enabled = false
project.save
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
end
@@ -50,7 +50,7 @@ describe Projects::IssuesController do
controller.instance_variable_set(:@project, project)
allow(project).to receive(:default_issues_tracker?).and_return(false)
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
end
end
@@ -67,8 +67,8 @@ describe Projects::IssuesController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace.path.to_param,
- project_id: project.path.to_param,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
@@ -76,8 +76,8 @@ describe Projects::IssuesController do
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace.path.to_param,
- project_id: project.path.to_param,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
page: last_page.to_param
expect(assigns(:issues).current_page).to eq(last_page)
@@ -94,7 +94,7 @@ describe Projects::IssuesController do
end
it 'builds a new issue' do
- get :new, namespace_id: project.namespace.path, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
expect(assigns(:issue)).to be_a_new(Issue)
end
@@ -104,7 +104,7 @@ describe Projects::IssuesController do
project_with_repository.team << [user, :developer]
mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
- get :new, namespace_id: project_with_repository.namespace.path, project_id: project_with_repository, merge_request_for_resolving_discussions: mr.iid
+ get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_for_resolving_discussions: mr.iid
expect(assigns(:issue).title).not_to be_empty
expect(assigns(:issue).description).not_to be_empty
@@ -117,7 +117,7 @@ describe Projects::IssuesController do
allow(project).to receive(:external_issue_tracker).and_return(external)
controller.instance_variable_set(:@project, project)
- get :new, namespace_id: project.namespace.path, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
expect(response).to redirect_to('https://example.com/issues/new')
end
@@ -125,14 +125,16 @@ describe Projects::IssuesController do
end
describe 'PUT #update' do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it_behaves_like 'update invalid issuable', Issue
+
context 'when moving issue to another private project' do
let(:another_project) { create(:empty_project, :private) }
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
-
context 'when user has access to move issue' do
before { another_project.team << [user, :reporter] }
@@ -251,7 +253,7 @@ describe Projects::IssuesController do
def update_issue(issue_params = {}, additional_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: issue.iid,
issue: issue_params
}.merge(additional_params)
@@ -262,7 +264,7 @@ describe Projects::IssuesController do
def move_issue
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: issue.iid,
issue: { title: 'New title' },
move_to_project_id: another_project.id
@@ -342,7 +344,7 @@ describe Projects::IssuesController do
def get_issues
get :index,
namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ project_id: project
end
end
@@ -405,7 +407,7 @@ describe Projects::IssuesController do
def go(id:)
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id
end
end
@@ -416,7 +418,7 @@ describe Projects::IssuesController do
def go(id:)
get :edit,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id
end
end
@@ -427,7 +429,7 @@ describe Projects::IssuesController do
def go(id:)
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id,
issue: { title: 'New title' }
end
@@ -442,7 +444,7 @@ describe Projects::IssuesController do
post :create, {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
issue: { title: 'Title', description: 'Description' }.merge(issue_attrs)
}.merge(additional_params)
@@ -464,7 +466,7 @@ describe Projects::IssuesController do
end
def post_issue(issue_params)
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, issue: issue_params, merge_request_for_resolving_discussions: merge_request.iid
+ post :create, namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_for_resolving_discussions: merge_request.iid
end
it 'creates an issue for the project' do
@@ -607,8 +609,8 @@ describe Projects::IssuesController do
project.team << [admin, :master]
sign_in(admin)
post :mark_as_spam, {
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: issue.iid
}
end
@@ -624,7 +626,7 @@ describe Projects::IssuesController do
context "when the user is a developer" do
before { sign_in(user) }
it "rejects a developer to destroy an issue" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(404)
end
end
@@ -637,7 +639,7 @@ describe Projects::IssuesController do
before { sign_in(owner) }
it "deletes the issue" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
@@ -646,7 +648,7 @@ describe Projects::IssuesController do
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
end
end
end
@@ -659,8 +661,8 @@ describe Projects::IssuesController do
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, namespace_id: project.namespace.path,
- project_id: project.path, id: issue.iid, name: "thumbsup")
+ post(:toggle_award_emoji, namespace_id: project.namespace,
+ project_id: project, id: issue.iid, name: "thumbsup")
end.to change { issue.award_emoji.count }.by(1)
expect(response).to have_http_status(200)
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index 3e0326dd47d..6a6e9bf378a 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -67,7 +67,7 @@ describe Projects::LabelsController do
end
def list_labels
- get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
+ get :index, namespace_id: project.namespace.to_param, project_id: project
end
end
@@ -76,7 +76,7 @@ describe Projects::LabelsController do
let(:personal_project) { create(:empty_project, namespace: user.namespace) }
it 'creates labels' do
- post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project.to_param
+ post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project
expect(response).to have_http_status(302)
end
@@ -84,7 +84,7 @@ describe Projects::LabelsController do
context 'project belonging to a group' do
it 'creates labels' do
- post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param
+ post :generate, namespace_id: project.namespace.to_param, project_id: project
expect(response).to have_http_status(302)
end
@@ -109,7 +109,7 @@ describe Projects::LabelsController do
end
def toggle_subscription(label)
- post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param
+ post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project, id: label.to_param
end
end
@@ -119,7 +119,7 @@ describe Projects::LabelsController do
context 'not group owner' do
it 'denies access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to have_http_status(404)
end
@@ -131,13 +131,13 @@ describe Projects::LabelsController do
end
it 'gives access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to redirect_to(namespace_project_labels_path)
end
it 'promotes the label' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(Label.where(id: label_1.id)).to be_empty
expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil
@@ -151,7 +151,7 @@ describe Projects::LabelsController do
end
it 'returns to label list' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
expect(response).to redirect_to(namespace_project_labels_path)
end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index cae733f0cfb..c5abf11cfa5 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::MattermostsController do
it 'accepts the request' do
get(:new,
namespace_id: project.namespace.to_param,
- project_id: project.to_param)
+ project_id: project)
expect(response).to have_http_status(200)
end
@@ -30,7 +30,7 @@ describe Projects::MattermostsController do
subject do
post(:create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
mattermost: mattermost_params)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index af13649eec5..1ced666bb36 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -51,7 +51,7 @@ describe Projects::MergeRequestsController do
def submit_new_merge_request(format: :html)
get :new,
namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
+ project_id: fork_project,
merge_request: {
source_branch: 'remove-submodule',
target_branch: 'master'
@@ -64,7 +64,7 @@ describe Projects::MergeRequestsController do
it "loads labels into the @labels variable" do
get action,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: 'html'
expect(assigns(:labels)).not_to be_nil
@@ -76,7 +76,7 @@ describe Projects::MergeRequestsController do
it "does generally work" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -90,7 +90,7 @@ describe Projects::MergeRequestsController do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
end
@@ -98,7 +98,7 @@ describe Projects::MergeRequestsController do
it "renders it" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -111,7 +111,7 @@ describe Projects::MergeRequestsController do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format)
@@ -126,7 +126,7 @@ describe Projects::MergeRequestsController do
it "triggers workhorse to serve the request" do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :diff)
@@ -138,7 +138,7 @@ describe Projects::MergeRequestsController do
it 'triggers workhorse to serve the request' do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :patch)
@@ -153,7 +153,7 @@ describe Projects::MergeRequestsController do
def get_merge_requests(page = nil)
get :index,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
state: 'opened', page: page.to_param
end
@@ -216,8 +216,8 @@ describe Projects::MergeRequestsController do
it 'closes MR without errors' do
post :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
state_event: 'close'
@@ -231,8 +231,8 @@ describe Projects::MergeRequestsController do
merge_request.close!
put :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
title: 'New title'
@@ -246,8 +246,8 @@ describe Projects::MergeRequestsController do
merge_request.close!
put :update,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
merge_request: {
target_branch: 'new_branch'
@@ -255,14 +255,16 @@ describe Projects::MergeRequestsController do
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
end
+
+ it_behaves_like 'update invalid issuable', MergeRequest
end
end
describe 'POST merge' do
let(:base_params) do
{
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
format: 'raw'
}
@@ -426,7 +428,7 @@ describe Projects::MergeRequestsController do
describe "DELETE destroy" do
it "denies access to users unless they're admin or project owner" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(404)
end
@@ -439,7 +441,7 @@ describe Projects::MergeRequestsController do
before { sign_in owner }
it "deletes the merge request" do
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
@@ -448,7 +450,7 @@ describe Projects::MergeRequestsController do
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once
- delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+ delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
end
end
end
@@ -457,7 +459,7 @@ describe Projects::MergeRequestsController do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid
}
@@ -537,7 +539,7 @@ describe Projects::MergeRequestsController do
def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ project_id: project
}
get :diff_for_path, params.merge(extra_params)
@@ -601,7 +603,7 @@ describe Projects::MergeRequestsController do
before do
other_project.team << [user, :master]
- diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param)
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project)
end
it 'returns a 404' do
@@ -667,7 +669,7 @@ describe Projects::MergeRequestsController do
def go(format: 'html')
get :commits,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: format
end
@@ -707,7 +709,7 @@ describe Projects::MergeRequestsController do
before do
get :pipelines,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid,
format: :json
end
@@ -726,7 +728,7 @@ describe Projects::MergeRequestsController do
get :conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json'
end
@@ -744,7 +746,7 @@ describe Projects::MergeRequestsController do
before do
get :conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json'
end
@@ -807,7 +809,7 @@ describe Projects::MergeRequestsController do
post :remove_wip,
namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project.to_param,
+ project_id: merge_request.project,
id: merge_request.iid
expect(merge_request.reload.title).to eq(merge_request.wipless_title)
@@ -818,7 +820,7 @@ describe Projects::MergeRequestsController do
def conflict_for_path(path)
get :conflict_for_path,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
old_path: path,
new_path: path,
@@ -874,7 +876,7 @@ describe Projects::MergeRequestsController do
def resolve_conflicts(files)
post :resolve_conflicts,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project.to_param,
+ project_id: merge_request_with_conflicts.project,
id: merge_request_with_conflicts.iid,
format: 'json',
files: files,
@@ -1025,7 +1027,7 @@ describe Projects::MergeRequestsController do
post :assign_related_issues,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.iid
end
@@ -1080,7 +1082,7 @@ describe Projects::MergeRequestsController do
get :ci_environments_status,
namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project.to_param,
+ project_id: merge_request.project,
id: merge_request.iid, format: 'json'
end
@@ -1093,8 +1095,8 @@ describe Projects::MergeRequestsController do
describe 'GET merge_widget_refresh' do
let(:params) do
{
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: merge_request.iid,
format: :raw
}
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 9a1e79c281a..04bb5cbbd59 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -17,8 +17,8 @@ describe Projects::PipelinesController do
create(:ci_empty_pipeline, status: 'created', project: project)
create(:ci_empty_pipeline, status: 'success', project: project)
- get :index, namespace_id: project.namespace.path,
- project_id: project.path,
+ get :index, namespace_id: project.namespace,
+ project_id: project,
format: :json
end
@@ -62,8 +62,8 @@ describe Projects::PipelinesController do
end
def get_stage(name)
- get :stage, namespace_id: project.namespace.path,
- project_id: project.path,
+ get :stage, namespace_id: project.namespace,
+ project_id: project,
id: pipeline.id,
stage: name,
format: :json
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index da6112a13f7..e378b5714fe 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -4,7 +4,7 @@ describe Projects::ProtectedBranchesController do
describe "GET #index" do
let(:project) { create(:project_empty_repo, :public) }
it "redirects empty repo to projects page" do
- get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param)
+ get(:index, namespace_id: project.namespace.to_param, project_id: project)
end
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index b23d6e257ba..4cebe3884bf 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::RawController do
it 'delivers ASCII file' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -27,7 +27,7 @@ describe Projects::RawController do
it 'sets image content type header' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -51,7 +51,7 @@ describe Projects::RawController do
expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment')
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(200)
@@ -62,7 +62,7 @@ describe Projects::RawController do
it 'does not serve the file' do
get(:show,
namespace_id: public_project.namespace.to_param,
- project_id: public_project.to_param,
+ project_id: public_project,
id: id)
expect(response).to have_http_status(404)
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index d8fb4667c67..3a3e7467ef2 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::RefsController do
def default_get(format = :html)
get :logs_tree,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: 'master',
path: 'foo/bar/baz.html',
format: format
@@ -23,7 +23,7 @@ describe Projects::RefsController do
xhr :get,
:logs_tree,
namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: 'master',
+ project_id: project, id: 'master',
path: 'foo/bar/baz.html', format: format
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 69fcc26c77e..358f26dfb02 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::ReleasesController do
tag_id = release.tag
project.releases.destroy_all
- get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: tag_id
+ get :edit, namespace_id: project.namespace, project_id: project, tag_id: tag_id
release = assigns(:release)
expect(release).not_to be_nil
@@ -24,7 +24,7 @@ describe Projects::ReleasesController do
end
it 'retrieves an existing release' do
- get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: release.tag
+ get :edit, namespace_id: project.namespace, project_id: project, tag_id: release.tag
release = assigns(:release)
expect(release).not_to be_nil
@@ -48,7 +48,7 @@ describe Projects::ReleasesController do
def update_release(description)
put :update,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
tag_id: release.tag,
release: { description: description }
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 04e88879fb8..9c55d159fa0 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
@@ -22,7 +22,7 @@ describe Projects::RepositoriesController do
end
it "uses Gitlab::Workhorse" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
@@ -33,7 +33,7 @@ describe Projects::RepositoriesController do
end
it "renders Not Found" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 8bab094a79e..24a59caff4e 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -17,16 +17,16 @@ describe Projects::SnippetsController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace.path,
- project_id: project.path, page: (last_page + 1).to_param
+ namespace_id: project.namespace,
+ project_id: project, page: (last_page + 1).to_param
expect(response).to redirect_to(namespace_project_snippets_path(page: last_page))
end
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace.path,
- project_id: project.path, page: last_page.to_param
+ namespace_id: project.namespace,
+ project_id: project, page: last_page.to_param
expect(assigns(:snippets).current_page).to eq(last_page)
expect(response).to have_http_status(200)
@@ -38,7 +38,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_http_status(200)
@@ -49,7 +49,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'renders the snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_http_status(200)
@@ -60,7 +60,7 @@ describe Projects::SnippetsController do
before { sign_in(user2) }
it 'renders the snippet' do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_http_status(200)
@@ -77,7 +77,7 @@ describe Projects::SnippetsController do
post :create, {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}.merge(additional_params)
end
@@ -152,7 +152,7 @@ describe Projects::SnippetsController do
put :update, {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: snippet.id,
project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}.merge(additional_params)
@@ -281,8 +281,8 @@ describe Projects::SnippetsController do
sign_in(admin)
post :mark_as_spam,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: snippet.id
end
@@ -300,7 +300,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(response).to have_http_status(404)
end
@@ -310,7 +310,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'renders the snippet' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_http_status(200)
@@ -321,7 +321,7 @@ describe Projects::SnippetsController do
before { sign_in(user2) }
it 'renders the snippet' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
+ get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_http_status(200)
@@ -332,7 +332,7 @@ describe Projects::SnippetsController do
context 'when the project snippet does not exist' do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+ get action, namespace_id: project.namespace, project_id: project, id: 42
expect(response).to have_http_status(404)
end
@@ -342,7 +342,7 @@ describe Projects::SnippetsController do
before { sign_in(user) }
it 'responds with status 404' do
- get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
+ get action, namespace_id: project.namespace, project_id: project, id: 42
expect(response).to have_http_status(404)
end
@@ -364,8 +364,8 @@ describe Projects::SnippetsController do
context 'CRLF line ending' do
let(:params) do
{
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
id: project_snippet.to_param
}
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index c36a5fdd66c..fc97bac64cd 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::TagsController do
let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
describe 'GET index' do
- before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+ before { get :index, namespace_id: project.namespace.to_param, project_id: project }
it 'returns the tags for the page' do
expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
@@ -19,7 +19,7 @@ describe Projects::TagsController do
end
describe 'GET show' do
- before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id }
+ before { get :show, namespace_id: project.namespace.to_param, project_id: project, id: id }
context "valid tag" do
let(:id) { 'v1.0.0' }
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 80f84a388ce..70e7f9ca96e 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -14,13 +14,13 @@ describe Projects::TemplatesController do
before do
project.add_user(user, Gitlab::Access::MASTER)
- project.repository.commit_file(user, file_path_1, 'something valid',
- message: 'test 3', branch_name: 'master', update: false)
+ project.repository.create_file(user, file_path_1, 'something valid',
+ message: 'test 3', branch_name: 'master')
end
describe '#show' do
it 'renders template name and content as json' do
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(200)
expect(body["name"]).to eq("bug")
@@ -29,21 +29,21 @@ describe Projects::TemplatesController do
it 'renders 404 when unauthorized' do
sign_in(user2)
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 when template type is not found' do
sign_in(user)
- get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project.path, format: :json)
+ get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 without errors' do
sign_in(user)
- expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project.path, format: :json) }.not_to raise_error
+ expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json) }.not_to raise_error
end
end
end
diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb
index 415c264e0dd..9a7beeff6fe 100644
--- a/spec/controllers/projects/todo_controller_spec.rb
+++ b/spec/controllers/projects/todo_controller_spec.rb
@@ -12,8 +12,8 @@ describe Projects::TodosController do
describe 'POST create' do
def go
post :create,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
issuable_id: issue.id,
issuable_type: 'issue',
format: 'html'
@@ -80,8 +80,8 @@ describe Projects::TodosController do
describe 'POST create' do
def go
post :create,
- namespace_id: project.namespace.path,
- project_id: project.path,
+ namespace_id: project.namespace,
+ project_id: project,
issuable_id: merge_request.id,
issuable_type: 'merge_request',
format: 'html'
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index b81645a3d2d..ab94e292e48 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::TreeController do
before do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id)
end
@@ -74,7 +74,7 @@ describe Projects::TreeController do
before do
get(:show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: id)
end
@@ -94,7 +94,7 @@ describe Projects::TreeController do
before do
post(:create_dir,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: 'master',
dir_name: path,
target_branch: target_branch,
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 0347e789576..699c6f77cec 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::UploadsController do
it "returns an error" do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
format: :json
expect(response).to have_http_status(422)
end
@@ -26,7 +26,7 @@ describe Projects::UploadsController do
before do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
file: jpg,
format: :json
end
@@ -41,7 +41,7 @@ describe Projects::UploadsController do
before do
post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
file: txt,
format: :json
end
@@ -57,7 +57,7 @@ describe Projects::UploadsController do
let(:go) do
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
secret: "123456",
filename: "image.jpg"
end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 9fa358f7d62..e3f3b4fe8eb 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::VariablesController do
describe 'POST #create' do
context 'variable is valid' do
it 'shows a success flash message' do
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "one", value: "two" }
expect(flash[:notice]).to include 'Variables were successfully updated.'
@@ -22,7 +22,7 @@ describe Projects::VariablesController do
context 'variable is invalid' do
it 'shows an alert flash message' do
- post :create, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "..one", value: "two" }
expect(response).to render_template("projects/variables/show")
@@ -40,7 +40,7 @@ describe Projects::VariablesController do
end
it 'shows a success flash message' do
- post :update, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :update, namespace_id: project.namespace.to_param, project_id: project,
id: variable.id, variable: { key: variable.key, value: 'two' }
expect(flash[:notice]).to include 'Variable was successfully updated.'
@@ -48,7 +48,7 @@ describe Projects::VariablesController do
end
it 'renders the action #show if the variable key is invalid' do
- post :update, namespace_id: project.namespace.to_param, project_id: project.to_param,
+ post :update, namespace_id: project.namespace.to_param, project_id: project,
id: variable.id, variable: { key: '?', value: variable.value }
expect(response).to have_http_status(200)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index e7aa8745b99..202759664a0 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -35,7 +35,7 @@ describe ProjectsController do
let(:private_project) { create(:empty_project, :private) }
it "does not initialize notification setting" do
- get :show, namespace_id: private_project.namespace.path, id: private_project.path
+ get :show, namespace_id: private_project.namespace, id: private_project
expect(assigns(:notification_setting)).to be_nil
end
end
@@ -43,7 +43,7 @@ describe ProjectsController do
context "user has access to project" do
context "and does not have notification setting" do
it "initializes notification as disabled" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:notification_setting).level).to eq("global")
end
end
@@ -56,7 +56,7 @@ describe ProjectsController do
end
it "shows current notification setting" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:notification_setting).level).to eq("watch")
end
end
@@ -71,7 +71,7 @@ describe ProjectsController do
end
it 'shows wiki homepage' do
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/_wiki')
end
@@ -79,7 +79,7 @@ describe ProjectsController do
it 'shows issues list page if wiki is disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template('projects/issues/_issues')
end
@@ -88,7 +88,7 @@ describe ProjectsController do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template("projects/_customize_workflow")
end
@@ -96,7 +96,7 @@ describe ProjectsController do
it 'shows activity if enabled by user' do
user.update_attribute(:project_view, 'activity')
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response).to render_template("projects/_activity")
end
@@ -113,7 +113,7 @@ describe ProjectsController do
before do
user.update_attributes(project_view: project_view)
- get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ get :show, namespace_id: empty_project.namespace, id: empty_project
end
it "renders the empty project view" do
@@ -133,7 +133,7 @@ describe ProjectsController do
before do
user.update_attributes(project_view: project_view)
- get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ get :show, namespace_id: empty_project.namespace, id: empty_project
end
it "renders the empty project view" do
@@ -154,7 +154,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('activity')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_activity')
end
@@ -162,7 +162,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('readme')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_readme')
end
@@ -170,7 +170,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('files')
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(response).to render_template('_files')
end
end
@@ -178,7 +178,7 @@ describe ProjectsController do
context "when requested with case sensitive namespace and project path" do
context "when there is a match with the same casing" do
it "loads the project" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ get :show, namespace_id: public_project.namespace, id: public_project
expect(assigns(:project)).to eq(public_project)
expect(response).to have_http_status(200)
@@ -187,10 +187,10 @@ describe ProjectsController do
context "when there is a match with different casing" do
it "redirects to the normalized path" do
- get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+ get :show, namespace_id: public_project.namespace, id: public_project.path.upcase
expect(assigns(:project)).to eq(public_project)
- expect(response).to redirect_to("/#{public_project.path_with_namespace}")
+ expect(response).to redirect_to("/#{public_project.full_path}")
end
end
end
@@ -208,7 +208,7 @@ describe ProjectsController do
project = create(:empty_project, pending_delete: true)
sign_in(user)
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, namespace_id: project.namespace, id: project
expect(response.status).to eq 404
end
@@ -218,7 +218,7 @@ describe ProjectsController do
it 'redirects to project page (format.html)' do
project = create(:project, :public)
- get :show, namespace_id: project.namespace.path, id: project.path, format: :git
+ get :show, namespace_id: project.namespace, id: project, format: :git
expect(response).to have_http_status(302)
expect(response).to redirect_to(namespace_project_path)
@@ -239,7 +239,7 @@ describe ProjectsController do
sign_in(admin)
put :update,
- namespace_id: project.namespace.to_param,
+ namespace_id: project.namespace,
id: project.id,
project: project_params
@@ -257,7 +257,7 @@ describe ProjectsController do
sign_in(admin)
orig_id = project.id
- delete :destroy, namespace_id: project.namespace.path, id: project.path
+ delete :destroy, namespace_id: project.namespace, id: project
expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to have_http_status(302)
@@ -277,7 +277,7 @@ describe ProjectsController do
project.merge_requests << merge_request
sign_in(admin)
- delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path
+ delete :destroy, namespace_id: fork_project.namespace, id: fork_project
expect(merge_request.reload.state).to eq('closed')
end
@@ -287,8 +287,8 @@ describe ProjectsController do
describe 'PUT #new_issue_address' do
subject do
put :new_issue_address,
- namespace_id: project.namespace.to_param,
- id: project.to_param
+ namespace_id: project.namespace,
+ id: project
user.reload
end
@@ -316,23 +316,23 @@ describe ProjectsController do
sign_in(user)
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: public_project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: public_project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_truthy
post(:toggle_star,
- namespace_id: public_project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: public_project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
end
it "does nothing if user is not signed in" do
post(:toggle_star,
- namespace_id: project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: project.namespace.to_param,
- id: public_project.to_param)
+ namespace_id: project.namespace,
+ id: public_project)
expect(user.starred?(public_project)).to be_falsey
end
end
@@ -366,8 +366,8 @@ describe ProjectsController do
it 'does nothing if project was not forked' do
delete(:remove_fork,
- namespace_id: unforked_project.namespace.to_param,
- id: unforked_project.to_param, format: :js)
+ namespace_id: unforked_project.namespace,
+ id: unforked_project, format: :js)
expect(flash[:notice]).to be_nil
expect(response).to render_template(:remove_fork)
@@ -377,8 +377,8 @@ describe ProjectsController do
it "does nothing if user is not signed in" do
delete(:remove_fork,
- namespace_id: project.namespace.to_param,
- id: project.to_param, format: :js)
+ namespace_id: project.namespace,
+ id: project, format: :js)
expect(response).to have_http_status(401)
end
end
@@ -387,7 +387,7 @@ describe ProjectsController do
let(:public_project) { create(:project, :public) }
it "gets a list of branches and tags" do
- get :refs, namespace_id: public_project.namespace.path, id: public_project.path
+ get :refs, namespace_id: public_project.namespace, id: public_project
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
@@ -396,7 +396,7 @@ describe ProjectsController do
end
it "gets a list of branches, tags and commits" do
- get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
+ get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456"
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index c80b09e9b9d..586efdefdb3 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -138,27 +138,24 @@ FactoryGirl.define do
project.add_user(args[:user], args[:access])
- project.repository.commit_file(
+ project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/bug.md",
'something valid',
message: 'test 3',
- branch_name: 'master',
- update: false)
- project.repository.commit_file(
+ branch_name: 'master')
+ project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/template_test.md",
'template_test',
message: 'test 1',
- branch_name: 'master',
- update: false)
- project.repository.commit_file(
+ branch_name: 'master')
+ project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/feature_proposal.md",
'feature_proposal',
message: 'test 2',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
end
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 1732b1a0081..249dabbaae8 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -26,6 +26,11 @@ FactoryGirl.define do
two_factor_via_otp
end
+ trait :ghost do
+ ghost true
+ after(:build) { |user, _| user.block! }
+ end
+
trait :two_factor_via_otp do
before(:create) do |user|
user.otp_required_for_login = true
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index e8e080ce3e2..273cacd82cd 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -32,7 +32,7 @@ feature 'Admin disables Git access protocol', feature: true do
scenario 'shows only HTTP url' do
visit_project
- expect(page).to have_content("git clone #{project.http_url_to_repo}")
+ expect(page).to have_content("git clone #{project.http_url_to_repo(admin)}")
expect(page).not_to have_selector('#clone-dropdown')
end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index f8c3ccb416b..b740e191f48 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -61,7 +61,7 @@ describe "User Feed", feature: true do
end
it 'has XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/
+ expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end
end
end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index ba77093a6d4..49d93db58a9 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -12,7 +12,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
- visit activity_namespace_project_path(project.namespace.path, project.path)
+ visit activity_namespace_project_path(project.namespace, project)
wait_for_ajax
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 1e0db4a0499..1c8267b1593 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe 'Issues', feature: true do
+ include DropzoneHelper
include IssueHelpers
include SortingHelper
include WaitForAjax
@@ -570,19 +571,13 @@ describe 'Issues', feature: true do
end
it 'uploads file when dragging into textarea' do
- drop_in_dropzone test_image_file
-
- # Wait for the file to upload
- sleep 1
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
it 'adds double newline to end of attachment markdown' do
- drop_in_dropzone test_image_file
-
- # Wait for the file to upload
- sleep 1
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to match /\n\n$/
end
@@ -665,25 +660,4 @@ describe 'Issues', feature: true do
end
end
end
-
- def drop_in_dropzone(file_path)
- # Generate a fake input selector
- page.execute_script <<-JS
- var fakeFileInput = window.$('<input/>').attr(
- {id: 'fakeFileInput', type: 'file'}
- ).appendTo('body');
- JS
- # Attach the file to the fake input selector with Capybara
- attach_file("fakeFileInput", file_path)
- # Add the file to a fileList array and trigger the fake drop event
- page.execute_script <<-JS
- var fileList = [$('#fakeFileInput')[0].files[0]];
- var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
- $('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
- JS
- end
-
- def test_image_file
- File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
- end
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 7a562b5e03d..406d7cf791c 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -4,7 +4,7 @@ describe 'Profile account page', feature: true do
let(:user) { create(:user) }
before do
- login_as :user
+ login_as(user)
end
describe 'when signup is enabled' do
@@ -16,7 +16,7 @@ describe 'Profile account page', feature: true do
it { expect(page).to have_content('Remove account') }
it 'deletes the account' do
- expect { click_link 'Delete account' }.to change { User.count }.by(-1)
+ expect { click_link 'Delete account' }.to change { User.where(id: user.id).count }.by(-1)
expect(current_path).to eq(new_user_session_path)
end
end
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index eb1050d21c6..2f436f153aa 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -15,7 +15,7 @@ feature 'Profile > SSH Keys', feature: true do
scenario 'auto-populates the title', js: true do
fill_in('Key', with: attributes_for(:key).fetch(:key))
- expect(find_field('Title').value).to eq 'dummy@gitlab.com'
+ expect(page).to have_field("Title", with: "dummy@gitlab.com")
end
scenario 'saves the new key' do
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index 0c51fe72ca4..2352329d58c 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -56,8 +56,14 @@ feature 'Developer views empty project instructions', feature: true do
end
def expect_instructions_for(protocol)
- msg = :"#{protocol.downcase}_url_to_repo"
-
- expect(page).to have_content("git clone #{project.send(msg)}")
+ url =
+ case protocol
+ when 'ssh'
+ project.ssh_url_to_repo
+ when 'http'
+ project.http_url_to_repo(developer)
+ end
+
+ expect(page).to have_content("git clone #{url}")
end
end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index f8ef4577a26..ccadc936567 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -6,7 +6,7 @@ feature 'project owner creates a license file', feature: true, js: true do
let(:project_master) { create(:user) }
let(:project) { create(:project) }
background do
- project.repository.remove_file(project_master, 'LICENSE',
+ project.repository.delete_file(project_master, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
project.team << [project_master, :master]
login_as(project_master)
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index e90a033b8c4..62d0aedda48 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -18,20 +18,18 @@ feature 'issuable templates', feature: true, js: true do
let(:description_addition) { ' appending to description' }
background do
- project.repository.commit_file(
+ project.repository.create_file(
user,
'.gitlab/issue_templates/bug.md',
template_content,
message: 'added issue template',
- branch_name: 'master',
- update: false)
- project.repository.commit_file(
+ branch_name: 'master')
+ project.repository.create_file(
user,
'.gitlab/issue_templates/test.md',
longtemplate_content,
message: 'added issue template',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title'
end
@@ -79,13 +77,12 @@ feature 'issuable templates', feature: true, js: true do
let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do
- project.repository.commit_file(
+ project.repository.create_file(
user,
'.gitlab/issue_templates/bug.md',
template_content,
message: 'added issue template',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title'
fill_in :'issue[description]', with: prior_description
@@ -104,13 +101,12 @@ feature 'issuable templates', feature: true, js: true do
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
background do
- project.repository.commit_file(
+ project.repository.create_file(
user,
'.gitlab/merge_request_templates/feature-proposal.md',
template_content,
message: 'added merge request template',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
@@ -135,13 +131,12 @@ feature 'issuable templates', feature: true, js: true do
fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
login_as fork_user
- project.repository.commit_file(
+ project.repository.create_file(
fork_user,
'.gitlab/merge_request_templates/feature-proposal.md',
template_content,
message: 'added merge request template',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index d6ebb523f95..c7a32a65e49 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -85,7 +85,7 @@ feature 'Projects > Members > Sorting', feature: true do
end
def visit_members_list(sort:)
- visit namespace_project_project_members_path(project.namespace.to_param, project.to_param, sort: sort)
+ visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort)
end
def first_member
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 18ec31f3280..22bf1bfbdf0 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -371,8 +371,14 @@ describe 'Pipelines', :feature, :js do
visit new_namespace_project_pipeline_path(project.namespace, project)
end
- context 'for valid commit' do
- before { fill_in('pipeline[ref]', with: 'master') }
+ context 'for valid commit', js: true do
+ before do
+ click_button project.default_branch
+
+ page.within '.dropdown-menu' do
+ click_link 'master'
+ end
+ end
context 'with gitlab-ci.yml' do
before { stub_ci_pipeline_to_return_yaml_file }
@@ -389,15 +395,6 @@ describe 'Pipelines', :feature, :js do
it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
end
end
-
- context 'for invalid commit' do
- before do
- fill_in('pipeline[ref]', with: 'invalid-reference')
- click_on 'Create pipeline'
- end
-
- it { expect(page).to have_content('Reference not found') }
- end
end
describe 'Create pipelines' do
@@ -409,18 +406,22 @@ describe 'Pipelines', :feature, :js do
describe 'new pipeline page' do
it 'has field to add a new pipeline' do
- expect(page).to have_field('pipeline[ref]')
+ expect(page).to have_selector('.js-branch-select')
+ expect(find('.js-branch-select')).to have_content project.default_branch
expect(page).to have_content('Create for')
end
end
describe 'find pipelines' do
it 'shows filtered pipelines', js: true do
- fill_in('pipeline[ref]', with: 'fix')
- find('input#ref').native.send_keys(:keydown)
+ click_button project.default_branch
- within('.ui-autocomplete') do
- expect(page).to have_selector('li', text: 'fix')
+ page.within '.dropdown-menu' do
+ find('.dropdown-input-field').native.send_keys('fix')
+
+ page.within '.dropdown-content' do
+ expect(page).to have_content('fix')
+ end
end
end
end
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
new file mode 100644
index 00000000000..f88a515f7fc
--- /dev/null
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+feature 'User uploads avatar to group', feature: true do
+ scenario 'they see the new avatar' do
+ user = create(:user)
+ group = create(:group)
+ group.add_owner(user)
+ login_as(user)
+
+ visit edit_group_path(group)
+ attach_file(
+ 'group_avatar',
+ Rails.root.join('spec', 'fixtures', 'dk.png'),
+ visible: false
+ )
+
+ click_button 'Save group'
+
+ visit group_path(group)
+
+ expect(page).to have_selector(%Q(img[src$="/uploads/group/avatar/#{group.id}/dk.png"]))
+
+ # Cheating here to verify something that isn't user-facing, but is important
+ expect(group.reload.avatar.file).to exist
+ end
+end
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
new file mode 100644
index 00000000000..0dfd29045e5
--- /dev/null
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+feature 'User uploads avatar to profile', feature: true do
+ scenario 'they see their new avatar' do
+ user = create(:user)
+ login_as(user)
+
+ visit profile_path
+ attach_file(
+ 'user_avatar',
+ Rails.root.join('spec', 'fixtures', 'dk.png'),
+ visible: false
+ )
+
+ click_button 'Update profile settings'
+
+ visit user_path(user)
+
+ expect(page).to have_selector(%Q(img[src$="/uploads/user/avatar/#{user.id}/dk.png"]))
+
+ # Cheating here to verify something that isn't user-facing, but is important
+ expect(user.reload.avatar.file).to exist
+ end
+end
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
new file mode 100644
index 00000000000..0c160dd74b4
--- /dev/null
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+feature 'User uploads file to note', feature: true do
+ include DropzoneHelper
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, creator: user, namespace: user.namespace) }
+
+ scenario 'they see the attached file', js: true do
+ issue = create(:issue, project: project, author: user)
+
+ login_as(user)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ dropzone_file(Rails.root.join('spec', 'fixtures', 'dk.png'))
+ click_button 'Comment'
+ wait_for_ajax
+
+ expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
+ .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
+ end
+end
diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb
new file mode 100644
index 00000000000..336c4092c98
--- /dev/null
+++ b/spec/features/user_callout_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe 'User Callouts', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
+
+ before do
+ login_as(user)
+ project.team << [user, :master]
+ end
+
+ it 'takes you to the profile preferences when the link is clicked' do
+ visit dashboard_projects_path
+ click_link 'Check it out'
+ expect(current_path).to eq profile_preferences_path
+ end
+
+ describe 'user callout should appear in two routes' do
+ it 'shows up on the user profile' do
+ visit user_path(user)
+ expect(find('.user-callout')).to have_content 'Customize your experience'
+ end
+
+ it 'shows up on the dashboard projects' do
+ visit dashboard_projects_path
+ expect(find('.user-callout')).to have_content 'Customize your experience'
+ end
+ end
+
+ it 'hides the user callout when click on the dismiss icon' do
+ visit user_path(user)
+ within('.user-callout') do
+ find('.close-user-callout').click
+ end
+ expect(page).not_to have_selector('#user-callout')
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index fd40fe99941..4ffdd530171 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -58,7 +58,7 @@ describe ApplicationHelper do
project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/project/avatar/#{project.id}/banana_sample.gif"
- expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
+ expect(helper.project_icon(project.full_path).to_s).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
end
@@ -68,7 +68,7 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
avatar_url = "http://#{Gitlab.config.gitlab.host}#{namespace_project_avatar_path(project.namespace, project)}"
- expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match(
+ expect(helper.project_icon(project.full_path).to_s).to match(
image_tag(avatar_url))
end
end
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
new file mode 100644
index 00000000000..192916fbc6a
--- /dev/null
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -0,0 +1,168 @@
+/* global Vue */
+/* global List */
+/* global ListLabel */
+/* global listObj */
+/* global boardsMockInterceptor */
+/* global BoardService */
+
+require('~/boards/models/list');
+require('~/boards/models/label');
+require('~/boards/stores/boards_store');
+const boardCard = require('~/boards/components/board_card');
+require('./mock_data');
+
+describe('Issue card', () => {
+ let vm;
+
+ beforeEach((done) => {
+ Vue.http.interceptors.push(boardsMockInterceptor);
+
+ gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.issueBoards.BoardsStore.create();
+ gl.issueBoards.BoardsStore.detail.issue = {};
+
+ const BoardCardComp = Vue.extend(boardCard);
+ const list = new List(listObj);
+ const label1 = new ListLabel({
+ id: 3,
+ title: 'testing 123',
+ color: 'blue',
+ text_color: 'white',
+ description: 'test',
+ });
+
+ setTimeout(() => {
+ list.issues[0].labels.push(label1);
+
+ vm = new BoardCardComp({
+ propsData: {
+ list,
+ issue: list.issues[0],
+ issueLinkBase: '/',
+ disabled: false,
+ index: 0,
+ rootPath: '/',
+ },
+ }).$mount();
+ done();
+ }, 0);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+ });
+
+ it('returns false when detailIssue is empty', () => {
+ expect(vm.issueDetailVisible).toBe(false);
+ });
+
+ it('returns true when detailIssue is equal to card issue', () => {
+ gl.issueBoards.BoardsStore.detail.issue = vm.issue;
+
+ expect(vm.issueDetailVisible).toBe(true);
+ });
+
+ it('adds user-can-drag class if not disabled', () => {
+ expect(vm.$el.classList.contains('user-can-drag')).toBe(true);
+ });
+
+ it('does not add user-can-drag class disabled', (done) => {
+ vm.disabled = true;
+
+ setTimeout(() => {
+ expect(vm.$el.classList.contains('user-can-drag')).toBe(false);
+ done();
+ }, 0);
+ });
+
+ it('does not add disabled class', () => {
+ expect(vm.$el.classList.contains('is-disabled')).toBe(false);
+ });
+
+ it('adds disabled class is disabled is true', (done) => {
+ vm.disabled = true;
+
+ setTimeout(() => {
+ expect(vm.$el.classList.contains('is-disabled')).toBe(true);
+ done();
+ }, 0);
+ });
+
+ describe('mouse events', () => {
+ const triggerEvent = (eventName, el = vm.$el) => {
+ const event = document.createEvent('MouseEvents');
+ event.initMouseEvent(eventName, true, true, window, 1, 0, 0, 0, 0, false, false,
+ false, false, 0, null);
+
+ el.dispatchEvent(event);
+ };
+
+ it('sets showDetail to true on mousedown', () => {
+ triggerEvent('mousedown');
+
+ expect(vm.showDetail).toBe(true);
+ });
+
+ it('sets showDetail to false on mousemove', () => {
+ triggerEvent('mousedown');
+
+ expect(vm.showDetail).toBe(true);
+
+ triggerEvent('mousemove');
+
+ expect(vm.showDetail).toBe(false);
+ });
+
+ it('does not set detail issue if showDetail is false', () => {
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if link is clicked', () => {
+ triggerEvent('mouseup', vm.$el.querySelector('a'));
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if button is clicked', () => {
+ triggerEvent('mouseup', vm.$el.querySelector('button'));
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ });
+
+ it('does not set detail issue if showDetail is false after mouseup', () => {
+ triggerEvent('mouseup');
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ });
+
+ it('sets detail issue to card issue on mouse up', () => {
+ triggerEvent('mousedown');
+ triggerEvent('mouseup');
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue);
+ expect(gl.issueBoards.BoardsStore.detail.list).toEqual(vm.list);
+ });
+
+ it('adds active class if detail issue is set', (done) => {
+ triggerEvent('mousedown');
+ triggerEvent('mouseup');
+
+ setTimeout(() => {
+ expect(vm.$el.classList.contains('is-active')).toBe(true);
+ done();
+ }, 0);
+ });
+
+ it('resets detail issue to empty if already set', () => {
+ triggerEvent('mousedown');
+ triggerEvent('mouseup');
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue);
+
+ triggerEvent('mousedown');
+ triggerEvent('mouseup');
+
+ expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ });
+ });
+});
diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6
index 4397a32fedc..c8a18af7198 100644
--- a/spec/javascripts/boards/list_spec.js.es6
+++ b/spec/javascripts/boards/list_spec.js.es6
@@ -3,7 +3,9 @@
/* global boardsMockInterceptor */
/* global BoardService */
/* global List */
+/* global ListIssue */
/* global listObj */
+/* global listObjDuplicate */
require('~/lib/utils/url_utility');
require('~/boards/models/issue');
@@ -84,4 +86,23 @@ describe('List model', () => {
done();
}, 0);
});
+
+ it('sends service request to update issue label', () => {
+ const listDup = new List(listObjDuplicate);
+ const issue = new ListIssue({
+ title: 'Testing',
+ iid: 1,
+ confidential: false,
+ labels: [list.label, listDup.label]
+ });
+
+ list.issues.push(issue);
+ listDup.issues.push(issue);
+
+ spyOn(gl.boardService, 'moveIssue').and.callThrough();
+
+ listDup.updateIssueLabel(list, issue);
+
+ expect(gl.boardService.moveIssue).toHaveBeenCalledWith(issue.id, list.id, listDup.id);
+ });
});
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
index 0e7c2351b66..a059818145b 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/javascripts/fixtures/branches.rb
@@ -20,7 +20,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
it 'branches/new_branch.html.raw' do |example|
get :new,
namespace_id: project.namespace.to_param,
- project_id: project.to_param
+ project_id: project
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb
index 978e25a1c32..320de791b08 100644
--- a/spec/javascripts/fixtures/builds.rb
+++ b/spec/javascripts/fixtures/builds.rb
@@ -24,7 +24,7 @@ describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller
it 'builds/build-with-artifacts.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: build_with_artifacts.to_param
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 06f708f9e15..88e3f860809 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -41,7 +41,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
def render_issue(fixture_file_name, issue)
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: issue.to_param
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 62984097099..ee893b76c84 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -27,7 +27,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
def render_merge_request(fixture_file_name, merge_request)
get :show,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project,
id: merge_request.to_param
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 56513219e1e..6c33b240e5c 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -20,7 +20,7 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
it 'projects/dashboard.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,
- id: project.to_param
+ id: project
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
index 2c08b06ea9e..a81ef8c5492 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/javascripts/fixtures/todos.rb
@@ -39,8 +39,8 @@ describe 'Todos (JavaScript fixtures)' do
it 'todos/todos.json' do |example|
post :create,
- namespace_id: namespace.path,
- project_id: project.path,
+ namespace_id: namespace,
+ project_id: project,
issuable_type: 'issue',
issuable_id: issue_2.id,
format: 'json'
diff --git a/spec/javascripts/fixtures/user_callout.html.haml b/spec/javascripts/fixtures/user_callout.html.haml
new file mode 100644
index 00000000000..275359bde0a
--- /dev/null
+++ b/spec/javascripts/fixtures/user_callout.html.haml
@@ -0,0 +1,2 @@
+.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
+
diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
index a954bb60560..861f26e162f 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
@@ -1,9 +1,7 @@
-/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var, max-len */
-/* global d3 */
-/* global ContributorsGraph */
-/* global ContributorsMasterGraph */
+/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */
-require('~/graphs/stat_graph_contributors_graph');
+import d3 from 'd3';
+import { ContributorsGraph, ContributorsMasterGraph } from '~/graphs/stat_graph_contributors_graph';
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index b15764abe8c..9b47ab62181 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -1,7 +1,6 @@
/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */
-/* global ContributorsStatGraphUtil */
-require('~/graphs/stat_graph_contributors_util');
+import ContributorsStatGraphUtil from '~/graphs/stat_graph_contributors_util';
describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () {
diff --git a/spec/javascripts/graphs/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js
deleted file mode 100644
index 876c23361bc..00000000000
--- a/spec/javascripts/graphs/stat_graph_spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* eslint-disable quotes */
-/* global StatGraph */
-
-require('~/graphs/stat_graph');
-
-describe("StatGraph", function () {
- describe("#get_log", function () {
- it("returns log", function () {
- StatGraph.log = "test";
- expect(StatGraph.get_log()).toBe("test");
- });
- });
-
- describe("#set_log", function () {
- it("sets the log", function () {
- StatGraph.set_log("test");
- expect(StatGraph.log).toBe("test");
- });
- });
-});
diff --git a/spec/javascripts/user_callout_spec.js.es6 b/spec/javascripts/user_callout_spec.js.es6
new file mode 100644
index 00000000000..6ee63f56a26
--- /dev/null
+++ b/spec/javascripts/user_callout_spec.js.es6
@@ -0,0 +1,37 @@
+const UserCallout = require('~/user_callout');
+
+const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
+const Cookie = window.Cookies;
+
+describe('UserCallout', () => {
+ const fixtureName = 'static/user_callout.html.raw';
+ preloadFixtures(fixtureName);
+
+ beforeEach(function () {
+ loadFixtures(fixtureName);
+ this.userCallout = new UserCallout();
+ this.closeButton = $('.close-user-callout');
+ this.userCalloutBtn = $('.user-callout-btn');
+ this.userCalloutContainer = $('.user-callout');
+ Cookie.set(USER_CALLOUT_COOKIE, 'false');
+ });
+
+ afterEach(function () {
+ Cookie.set(USER_CALLOUT_COOKIE, 'false');
+ });
+
+ it('shows when cookie is set to false', function () {
+ expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined();
+ expect(this.userCalloutContainer.is(':visible')).toBe(true);
+ });
+
+ it('hides when user clicks on the dismiss-icon', function () {
+ this.closeButton.click();
+ expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
+ });
+
+ it('hides when user clicks on the "check it out" button', function () {
+ this.userCalloutBtn.click();
+ expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true');
+ });
+});
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index a2a1ed58d1b..294558b3db2 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -13,8 +13,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
end
it 'does not wrap a duplicate link' do
- exp = act = %q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>)
- expect(filter(act).to_html).to eq exp
+ doc = filter(%Q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>))
+ expect(doc.to_html).to match /^<a href="\/whatever"><img[^>]*><\/a>$/
end
it 'works with external images' do
@@ -22,8 +22,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end
- it 'wraps the image with a link and a div' do
- doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.to_html).to include('<div class="image-container">')
+ it 'works with inline images' do
+ doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
+ expect(doc.to_html).to match /^<p>test <a[^>]*><img[^>]*><\/a> inline<\/p>$/
end
end
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index a5251e9a8c2..4f25ad88960 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -6,7 +6,7 @@ describe ProjectUrlConstrainer, lib: true do
describe '#matches?' do
context 'valid request' do
- let(:request) { build_request(namespace.path, project.path) }
+ let(:request) { build_request(namespace.full_path, project.path) }
it { expect(subject.matches?(request)).to be_truthy }
end
@@ -19,7 +19,7 @@ describe ProjectUrlConstrainer, lib: true do
end
context "project id ending with .git" do
- let(:request) { build_request(namespace.path, project.path + '.git') }
+ let(:request) { build_request(namespace.full_path, project.path + '.git') }
it { expect(subject.matches?(request)).to be_falsey }
end
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 7e5531d92dc..780ac0ad97e 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -251,7 +251,7 @@ FILE
describe '#as_json' do
it 'includes the blob path for the file' do
expect(conflict_file.as_json[:blob_path]).
- to eq("/#{project.namespace.to_param}/#{merge_request.project.to_param}/blob/#{our_commit.oid}/files/ruby/regex.rb")
+ to eq("/#{project.full_path}/blob/#{our_commit.oid}/files/ruby/regex.rb")
end
it 'includes the blob icon for the file' do
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 0c321f0343c..8049e2c120d 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -222,191 +222,6 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
- describe :commit do
- let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
-
- let(:commit_options) do
- {
- file: {
- content: 'Lorem ipsum...',
- path: 'documents/story.txt'
- },
- author: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- committer: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- commit: {
- message: 'Wow such commit',
- branch: 'fix-mode'
- }
- }
- end
-
- let(:commit_sha) { Gitlab::Git::Blob.commit(repository, commit_options) }
- let(:commit) { repository.lookup(commit_sha) }
-
- it 'should add file with commit' do
- # Commit message valid
- expect(commit.message).to eq('Wow such commit')
-
- tree = commit.tree.to_a.find { |tree| tree[:name] == 'documents' }
-
- # Directory was created
- expect(tree[:type]).to eq(:tree)
-
- # File was created
- expect(repository.lookup(tree[:oid]).first[:name]).to eq('story.txt')
- end
-
- describe "ref updating" do
- it 'creates a commit but does not udate a ref' do
- commit_opts = commit_options.tap{ |opts| opts[:commit][:update_ref] = false}
- commit_sha = Gitlab::Git::Blob.commit(repository, commit_opts)
- commit = repository.lookup(commit_sha)
-
- # Commit message valid
- expect(commit.message).to eq('Wow such commit')
-
- # Does not update any related ref
- expect(repository.lookup("fix-mode").oid).not_to eq(commit.oid)
- expect(repository.lookup("HEAD").oid).not_to eq(commit.oid)
- end
- end
-
- describe 'reject updates' do
- it 'should reject updates' do
- commit_options[:file][:update] = false
- commit_options[:file][:path] = 'files/executables/ls'
-
- expect{ commit_sha }.to raise_error('Filename already exists; update not allowed')
- end
- end
-
- describe 'file modes' do
- it 'should preserve file modes with commit' do
- commit_options[:file][:path] = 'files/executables/ls'
-
- entry = Gitlab::Git::Blob.find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path])
- expect(entry[:filemode]).to eq(0100755)
- end
- end
- end
-
- describe :rename do
- let(:repository) { Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) }
- let(:rename_options) do
- {
- file: {
- path: 'NEWCONTRIBUTING.md',
- previous_path: 'CONTRIBUTING.md',
- content: 'Lorem ipsum...',
- update: true
- },
- author: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- committer: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- commit: {
- message: 'Rename readme',
- branch: 'master'
- }
- }
- end
-
- let(:rename_options2) do
- {
- file: {
- content: 'Lorem ipsum...',
- path: 'bin/new_executable',
- previous_path: 'bin/executable',
- },
- author: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- committer: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- commit: {
- message: 'Updates toberenamed.txt',
- branch: 'master',
- update_ref: false
- }
- }
- end
-
- it 'maintains file permissions when renaming' do
- mode = 0o100755
-
- Gitlab::Git::Blob.rename(repository, rename_options2)
-
- expect(repository.rugged.index.get(rename_options2[:file][:path])[:mode]).to eq(mode)
- end
-
- it 'renames the file with commit and not change file permissions' do
- ref = rename_options[:commit][:branch]
-
- expect(repository.rugged.index.get('CONTRIBUTING.md')).not_to be_nil
- expect { Gitlab::Git::Blob.rename(repository, rename_options) }.to change { repository.commit_count(ref) }.by(1)
-
- expect(repository.rugged.index.get('CONTRIBUTING.md')).to be_nil
- expect(repository.rugged.index.get('NEWCONTRIBUTING.md')).not_to be_nil
- end
- end
-
- describe :remove do
- let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
-
- let(:commit_options) do
- {
- file: {
- path: 'README.md'
- },
- author: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- committer: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- commit: {
- message: 'Remove readme',
- branch: 'feature'
- }
- }
- end
-
- let(:commit_sha) { Gitlab::Git::Blob.remove(repository, commit_options) }
- let(:commit) { repository.lookup(commit_sha) }
- let(:blob) { Gitlab::Git::Blob.find(repository, commit_sha, "README.md") }
-
- it 'should remove file with commit' do
- # Commit message valid
- expect(commit.message).to eq('Remove readme')
-
- # File was removed
- expect(blob).to be_nil
- end
- end
-
describe :lfs_pointers do
context 'file a valid lfs pointer' do
let(:blob) do
diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb
new file mode 100644
index 00000000000..d0c7ca60ddc
--- /dev/null
+++ b/spec/lib/gitlab/git/index_spec.rb
@@ -0,0 +1,220 @@
+require 'spec_helper'
+
+describe Gitlab::Git::Index, seed_helper: true do
+ let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
+ let(:index) { described_class.new(repository) }
+
+ before do
+ index.read_tree(repository.lookup('master').tree)
+ end
+
+ describe '#create' do
+ let(:options) do
+ {
+ content: 'Lorem ipsum...',
+ file_path: 'documents/story.txt'
+ }
+ end
+
+ context 'when no file at that path exists' do
+ it 'creates the file in the index' do
+ index.create(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(entry).not_to be_nil
+ expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
+ end
+ end
+
+ context 'when a file at that path exists' do
+ before do
+ options[:file_path] = 'files/executables/ls'
+ end
+
+ it 'raises an error' do
+ expect { index.create(options) }.to raise_error('Filename already exists')
+ end
+ end
+
+ context 'when content is in base64' do
+ before do
+ options[:content] = Base64.encode64(options[:content])
+ options[:encoding] = 'base64'
+ end
+
+ it 'decodes base64' do
+ index.create(options)
+
+ entry = index.get(options[:file_path])
+ expect(repository.lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
+ end
+ end
+
+ context 'when content contains CRLF' do
+ before do
+ repository.autocrlf = :input
+ options[:content] = "Hello,\r\nWorld"
+ end
+
+ it 'converts to LF' do
+ index.create(options)
+
+ entry = index.get(options[:file_path])
+ expect(repository.lookup(entry[:oid]).content).to eq("Hello,\nWorld")
+ end
+ end
+ end
+
+ describe '#create_dir' do
+ let(:options) do
+ {
+ file_path: 'newdir'
+ }
+ end
+
+ context 'when no file or dir at that path exists' do
+ it 'creates the dir in the index' do
+ index.create_dir(options)
+
+ entry = index.get(options[:file_path] + '/.gitkeep')
+
+ expect(entry).not_to be_nil
+ end
+ end
+
+ context 'when a file at that path exists' do
+ before do
+ options[:file_path] = 'files/executables/ls'
+ end
+
+ it 'raises an error' do
+ expect { index.create_dir(options) }.to raise_error('Directory already exists as a file')
+ end
+ end
+
+ context 'when a directory at that path exists' do
+ before do
+ options[:file_path] = 'files/executables'
+ end
+
+ it 'raises an error' do
+ expect { index.create_dir(options) }.to raise_error('Directory already exists')
+ end
+ end
+ end
+
+ describe '#update' do
+ let(:options) do
+ {
+ content: 'Lorem ipsum...',
+ file_path: 'README.md'
+ }
+ end
+
+ context 'when no file at that path exists' do
+ before do
+ options[:file_path] = 'documents/story.txt'
+ end
+
+ it 'raises an error' do
+ expect { index.update(options) }.to raise_error("File doesn't exist")
+ end
+ end
+
+ context 'when a file at that path exists' do
+ it 'updates the file in the index' do
+ index.update(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
+ end
+
+ it 'preserves file mode' do
+ options[:file_path] = 'files/executables/ls'
+
+ index.update(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(entry[:mode]).to eq(0100755)
+ end
+ end
+ end
+
+ describe '#move' do
+ let(:options) do
+ {
+ content: 'Lorem ipsum...',
+ previous_path: 'README.md',
+ file_path: 'NEWREADME.md'
+ }
+ end
+
+ context 'when no file at that path exists' do
+ it 'raises an error' do
+ options[:previous_path] = 'documents/story.txt'
+
+ expect { index.move(options) }.to raise_error("File doesn't exist")
+ end
+ end
+
+ context 'when a file at that path exists' do
+ it 'removes the old file in the index' do
+ index.move(options)
+
+ entry = index.get(options[:previous_path])
+
+ expect(entry).to be_nil
+ end
+
+ it 'creates the new file in the index' do
+ index.move(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(entry).not_to be_nil
+ expect(repository.lookup(entry[:oid]).content).to eq(options[:content])
+ end
+
+ it 'preserves file mode' do
+ options[:previous_path] = 'files/executables/ls'
+
+ index.move(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(entry[:mode]).to eq(0100755)
+ end
+ end
+ end
+
+ describe '#delete' do
+ let(:options) do
+ {
+ file_path: 'README.md'
+ }
+ end
+
+ context 'when no file at that path exists' do
+ before do
+ options[:file_path] = 'documents/story.txt'
+ end
+
+ it 'raises an error' do
+ expect { index.delete(options) }.to raise_error("File doesn't exist")
+ end
+ end
+
+ context 'when a file at that path exists' do
+ it 'removes the file in the index' do
+ index.delete(options)
+
+ entry = index.get(options[:file_path])
+
+ expect(entry).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 2a915bf426f..1919542ca25 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -529,7 +529,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
commit_with_new_name = nil
rename_commit = nil
- before(:all) do
+ before(:context) do
# Add new commits so that there's a renamed file in the commit history
repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
@@ -538,49 +538,119 @@ describe Gitlab::Git::Repository, seed_helper: true do
commit_with_new_name = new_commit_edit_new_file(repo)
end
+ after(:context) do
+ # Erase our commits so other tests get the original repo
+ repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
+ repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ end
+
context "where 'follow' == true" do
- options = { ref: "master", follow: true }
+ let(:options) { { ref: "master", follow: true } }
context "and 'path' is a directory" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding"))
- end
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "encoding"))
- it "should not follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_old_name)
+ end
end
end
context "and 'path' is a file that matches the new filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding/CHANGELOG"))
+ context 'without offset' do
+ it "follows renames" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
+
+ aggregate_failures do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
- it "should follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ context 'with offset=1' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
+
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
+ end
+
+ context 'with offset=1', 'and limit=1' do
+ it "follows renames, skip the latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
+
+ expect(log_commits).to contain_exactly(rename_commit)
+ end
+ end
+
+ context 'with offset=1', 'and limit=2' do
+ it "follows renames, skip the latest commit and return only two commits" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
+
+ aggregate_failures do
+ expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+ end
+ end
+ end
+
+ context 'with offset=2' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
+
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
+ end
+
+ context 'with offset=2', 'and limit=1' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+
+ expect(log_commits).to contain_exactly(commit_with_old_name)
+ end
+ end
+
+ context 'with offset=2', 'and limit=2' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
end
context "and 'path' is a file that matches the old filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "CHANGELOG"))
- end
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "CHANGELOG"))
- it "should not follow renames" do
- expect(log_commits).to include(commit_with_old_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_new_name)
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
end
end
context "unknown ref" do
- let(:log_commits) { repository.log(options.merge(ref: 'unknown')) }
+ it "returns an empty array" do
+ log_commits = repository.log(options.merge(ref: 'unknown'))
- it "should return empty" do
expect(log_commits).to eq([])
end
end
@@ -699,12 +769,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
end
-
- after(:all) do
- # Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
- end
end
describe "#commits_between" do
@@ -844,81 +908,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe '#mkdir' do
- let(:commit_options) do
- {
- author: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- committer: {
- email: 'user@example.com',
- name: 'Test User',
- time: Time.now
- },
- commit: {
- message: 'Test message',
- branch: 'refs/heads/fix',
- }
- }
- end
-
- def generate_diff_for_path(path)
- "diff --git a/#{path}/.gitkeep b/#{path}/.gitkeep
-new file mode 100644
-index 0000000..e69de29
---- /dev/null
-+++ b/#{path}/.gitkeep\n"
- end
-
- shared_examples 'mkdir diff check' do |path, expected_path|
- it 'creates a directory' do
- result = repository.mkdir(path, commit_options)
- expect(result).not_to eq(nil)
-
- # Verify another mkdir doesn't create a directory that already exists
- expect{ repository.mkdir(path, commit_options) }.to raise_error('Directory already exists')
- end
- end
-
- describe 'creates a directory in root directory' do
- it_should_behave_like 'mkdir diff check', 'new_dir', 'new_dir'
- end
-
- describe 'creates a directory in subdirectory' do
- it_should_behave_like 'mkdir diff check', 'files/ruby/test', 'files/ruby/test'
- end
-
- describe 'creates a directory in subdirectory with a slash' do
- it_should_behave_like 'mkdir diff check', '/files/ruby/test2', 'files/ruby/test2'
- end
-
- describe 'creates a directory in subdirectory with multiple slashes' do
- it_should_behave_like 'mkdir diff check', '//files/ruby/test3', 'files/ruby/test3'
- end
-
- describe 'handles relative paths' do
- it_should_behave_like 'mkdir diff check', 'files/ruby/../test_relative', 'files/test_relative'
- end
-
- describe 'creates nested directories' do
- it_should_behave_like 'mkdir diff check', 'files/missing/test', 'files/missing/test'
- end
-
- it 'does not attempt to create a directory with invalid relative path' do
- expect{ repository.mkdir('../files/missing/test', commit_options) }.to raise_error('Invalid path')
- end
-
- it 'does not attempt to overwrite a file' do
- expect{ repository.mkdir('README.md', commit_options) }.to raise_error('Directory already exists as a file')
- end
-
- it 'does not attempt to overwrite a directory' do
- expect{ repository.mkdir('files', commit_options) }.to raise_error('Directory already exists')
- end
- end
-
describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") }
let(:not_existed_branch) { repository.ls_files("not_existed_branch") }
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index d37890de9ae..48f7754bed8 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -209,13 +209,12 @@ describe Gitlab::GitAccess, lib: true do
stub_git_hooks
project.repository.add_branch(user, unprotected_branch, 'feature')
target_branch = project.repository.lookup('feature')
- source_branch = project.repository.commit_file(
+ source_branch = project.repository.create_file(
user,
FFaker::InternetSE.login_user_name,
FFaker::HipsterIpsum.paragraph,
message: FFaker::HipsterIpsum.sentence,
- branch_name: unprotected_branch,
- update: false)
+ branch_name: unprotected_branch)
rugged = project.repository.rugged
author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 20743811dab..f3fd0d82875 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::ImportExport, services: true do
end
it 'contains the namespace path' do
- expect(described_class.export_filename(project: project)).to include(project.namespace.full_path)
+ expect(described_class.export_filename(project: project)).to include(project.namespace.full_path.tr('/', '_'))
end
it 'does not go over a certain length' do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 089ec4e2737..ba45e2d758c 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -51,8 +51,8 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('foo-') }
end
- describe 'NAMESPACE_REF_REGEX_STR' do
- subject { %r{\A#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR}\z} }
+ describe 'FULL_NAMESPACE_REGEX_STR' do
+ subject { %r{\A#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR}\z} }
it { is_expected.to match('gitlab.org') }
it { is_expected.to match('gitlab.org/gitlab-git') }
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index c4486a32082..4e71597521d 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
subject { create(:abuse_report) }
- let(:user) { create(:user) }
+ let(:user) { create(:admin) }
it { expect(subject).to be_valid }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4251cb06a2d..b963ca4e542 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1245,8 +1245,8 @@ describe Ci::Build, :models do
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
- { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
+ { key: 'CI_PROJECT_PATH', value: project.full_path, 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/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index e008ec28fa4..677e60e1282 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -86,7 +86,7 @@ describe Group, 'Routable' do
let(:nested_group) { create(:group, parent: group) }
it { expect(group.full_path).to eq(group.path) }
- it { expect(nested_group.full_path).to eq("#{group.path}/#{nested_group.path}") }
+ it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
end
describe '#full_name' do
@@ -102,7 +102,7 @@ describe Project, 'Routable' do
describe '#full_path' do
let(:project) { build_stubbed(:empty_project) }
- it { expect(project.full_path).to eq "#{project.namespace.path}/#{project.path}" }
+ it { expect(project.full_path).to eq "#{project.namespace.full_path}/#{project.path}" }
end
describe '#full_name' do
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
new file mode 100644
index 00000000000..83187d732e4
--- /dev/null
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Uniquify, models: true do
+ let(:uniquify) { described_class.new }
+
+ describe "#string" do
+ it 'returns the given string if it does not exist' do
+ result = uniquify.string('test_string') { |s| false }
+
+ expect(result).to eq('test_string')
+ end
+
+ it 'returns the given string with a counter attached if the string exists' do
+ result = uniquify.string('test_string') { |s| s == 'test_string' }
+
+ expect(result).to eq('test_string1')
+ end
+
+ it 'increments the counter for each candidate string that also exists' do
+ result = uniquify.string('test_string') { |s| s == 'test_string' || s == 'test_string1' }
+
+ expect(result).to eq('test_string2')
+ end
+
+ it 'allows passing in a base function that defines the location of the counter' do
+ result = uniquify.string(-> (counter) { "test_#{counter}_string" }) do |s|
+ s == 'test__string'
+ end
+
+ expect(result).to eq('test_1_string')
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index b9fe492fe2c..e6a826a9418 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -21,13 +21,12 @@ describe 'CycleAnalytics#production', feature: true do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
- sha = context.project.repository.commit_file(
+ sha = context.project.repository.create_file(
context.user,
context.random_git_name,
'content',
message: 'commit message',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
context.project.repository.commit(sha)
context.deploy_master
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 78ec518ac86..3a02ed81adb 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -26,13 +26,12 @@ describe 'CycleAnalytics#staging', feature: true do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
- sha = context.project.repository.commit_file(
+ sha = context.project.repository.create_file(
context.user,
context.random_git_name,
'content',
message: 'commit message',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
context.project.repository.commit(sha)
context.deploy_master
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 35d932f1c64..3f9c4289de9 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -36,7 +36,7 @@ describe Namespace, models: true do
end
describe '#to_param' do
- it { expect(namespace.to_param).to eq(namespace.path) }
+ it { expect(namespace.to_param).to eq(namespace.full_path) }
end
describe '#human_name' do
@@ -151,7 +151,7 @@ describe Namespace, models: true do
describe :rm_dir do
let!(:project) { create(:empty_project, namespace: namespace) }
- let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) }
+ let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.full_path) }
it "removes its dirs when deleted" do
namespace.destroy
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 47ca802ebc2..044737c6026 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,7 +28,7 @@ describe DroneCiService, models: true, caching: true do
shared_context :drone_ci_service do
let(:drone) { DroneCiService.new }
let(:project) { create(:project, :repository, name: 'project') }
- let(:path) { "#{project.namespace.path}/#{project.path}" }
+ let(:path) { project.full_path }
let(:drone_url) { 'http://drone.example.com' }
let(:sha) { '2ab7834c' }
let(:branch) { 'dev' }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index b0087a9e15d..ee4f4092062 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -402,7 +402,7 @@ describe Project, models: true do
let(:project) { create(:empty_project, path: "somewhere") }
it 'returns the full web URL for this repo' do
- expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
+ expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
end
end
@@ -803,7 +803,7 @@ describe Project, models: true do
end
let(:avatar_path) do
- "/#{project.namespace.name}/#{project.path}/avatar"
+ "/#{project.full_path}/avatar"
end
it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
@@ -1148,16 +1148,14 @@ describe Project, models: true do
end
it 'renames a repository' do
- ns = project.namespace_dir
-
expect(gitlab_shell).to receive(:mv_repository).
ordered.
- with(project.repository_storage_path, "#{ns}/foo", "#{ns}/#{project.path}").
+ with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}").
and_return(true)
expect(gitlab_shell).to receive(:mv_repository).
ordered.
- with(project.repository_storage_path, "#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki").
+ with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki").
and_return(true)
expect_any_instance_of(SystemHooksService).
@@ -1166,7 +1164,7 @@ describe Project, models: true do
expect_any_instance_of(Gitlab::UploadsTransfer).
to receive(:rename_project).
- with('foo', project.path, ns)
+ with('foo', project.path, project.namespace.full_path)
expect(project).to receive(:expire_caches_before_rename)
@@ -1538,7 +1536,7 @@ describe Project, models: true do
it 'schedules a RepositoryForkWorker job' do
expect(RepositoryForkWorker).to receive(:perform_async).
with(project.id, forked_from_project.repository_storage_path,
- forked_from_project.path_with_namespace, project.namespace.path)
+ forked_from_project.path_with_namespace, project.namespace.full_path)
project.add_import_job
end
@@ -1752,7 +1750,7 @@ describe Project, models: true do
describe 'inside_path' do
let!(:project1) { create(:empty_project) }
let!(:project2) { create(:empty_project) }
- let!(:path) { project1.namespace.path }
+ let!(:path) { project1.namespace.full_path }
it { expect(Project.inside_path(path)).to eq([project1]) }
end
@@ -1767,7 +1765,7 @@ describe Project, models: true do
end
before do
- project.repository.commit_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master', update: false)
+ project.repository.create_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master')
end
context 'when there is a .gitlab/route-map.yml at the commit' do
@@ -1896,4 +1894,25 @@ describe Project, models: true do
end
end
end
+
+ describe '#http_url_to_repo' do
+ let(:project) { create :empty_project }
+
+ context 'when no user is given' do
+ it 'returns the url to the repo without a username' do
+ url = project.http_url_to_repo
+
+ expect(url).to eq(project.http_url_to_repo)
+ expect(url).not_to include('@')
+ end
+ end
+
+ context 'when user is given' do
+ it 'returns the url to the repo with the username' do
+ user = build_stubbed(:user)
+
+ expect(project.http_url_to_repo(user)).to match(%r{https?:\/\/#{user.username}@})
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 43aac31d8b7..ae203fada12 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -291,10 +291,10 @@ describe Repository, models: true do
end
end
- describe "#commit_dir" do
+ describe "#create_dir" do
it "commits a change that creates a new directory" do
expect do
- repository.commit_dir(user, 'newdir',
+ repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'master')
end.to change { repository.commits('master').count }.by(1)
@@ -307,7 +307,7 @@ describe Repository, models: true do
it "creates a fork and commit to the forked project" do
expect do
- repository.commit_dir(user, 'newdir',
+ repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'patch',
start_branch_name: 'master', start_project: forked_project)
end.to change { repository.commits('master').count }.by(0)
@@ -323,7 +323,7 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
- repository.commit_dir(user, 'newdir',
+ repository.create_dir(user, 'newdir',
message: 'Add newdir',
branch_name: 'master',
author_email: author_email, author_name: author_name)
@@ -337,25 +337,23 @@ describe Repository, models: true do
end
end
- describe "#commit_file" do
- it 'commits change to a file successfully' do
+ describe "#create_file" do
+ it 'commits new file successfully' do
expect do
- repository.commit_file(user, 'CHANGELOG', 'Changelog!',
- message: 'Updates file content',
- branch_name: 'master',
- update: true)
+ repository.create_file(user, 'NEWCHANGELOG', 'Changelog!',
+ message: 'Create changelog',
+ branch_name: 'master')
end.to change { repository.commits('master').count }.by(1)
- blob = repository.blob_at('master', 'CHANGELOG')
+ blob = repository.blob_at('master', 'NEWCHANGELOG')
expect(blob.data).to eq('Changelog!')
end
it 'respects the autocrlf setting' do
- repository.commit_file(user, 'hello.txt', "Hello,\r\nWorld",
+ repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
message: 'Add hello world',
- branch_name: 'master',
- update: true)
+ branch_name: 'master')
blob = repository.blob_at('master', 'hello.txt')
@@ -365,10 +363,9 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
- repository.commit_file(user, 'README', 'README!',
+ repository.create_file(user, 'NEWREADME', 'README!',
message: 'Add README',
branch_name: 'master',
- update: true,
author_email: author_email,
author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
@@ -382,6 +379,18 @@ describe Repository, models: true do
end
describe "#update_file" do
+ it 'updates file successfully' do
+ expect do
+ repository.update_file(user, 'CHANGELOG', 'Changelog!',
+ message: 'Update changelog',
+ branch_name: 'master')
+ end.to change { repository.commits('master').count }.by(1)
+
+ blob = repository.blob_at('master', 'CHANGELOG')
+
+ expect(blob.data).to eq('Changelog!')
+ end
+
it 'updates filename successfully' do
expect do
repository.update_file(user, 'NEWLICENSE', 'Copyright!',
@@ -398,9 +407,6 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
- repository.commit_file(user, 'README', 'README!',
- message: 'Add README', branch_name: 'master', update: true)
-
expect do
repository.update_file(user, 'README', 'Updated README!',
branch_name: 'master',
@@ -418,13 +424,10 @@ describe Repository, models: true do
end
end
- describe "#remove_file" do
+ describe "#delete_file" do
it 'removes file successfully' do
- repository.commit_file(user, 'README', 'README!',
- message: 'Add README', branch_name: 'master', update: true)
-
expect do
- repository.remove_file(user, 'README',
+ repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master')
end.to change { repository.commits('master').count }.by(1)
@@ -433,11 +436,8 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
- repository.commit_file(user, 'README', 'README!',
- message: 'Add README', branch_name: 'master', update: true)
-
expect do
- repository.remove_file(user, 'README',
+ repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master',
author_email: author_email, author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
@@ -587,14 +587,14 @@ describe Repository, models: true do
describe "#license_blob", caching: true do
before do
- repository.remove_file(
+ repository.delete_file(
user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
end
it 'handles when HEAD points to non-existent ref' do
- repository.commit_file(
+ repository.create_file(
user, 'LICENSE', 'Copyright!',
- message: 'Add LICENSE', branch_name: 'master', update: false)
+ message: 'Add LICENSE', branch_name: 'master')
allow(repository).to receive(:file_on_head).
and_raise(Rugged::ReferenceError)
@@ -603,27 +603,27 @@ describe Repository, models: true do
end
it 'looks in the root_ref only' do
- repository.remove_file(user, 'LICENSE',
+ repository.delete_file(user, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'markdown')
- repository.commit_file(user, 'LICENSE',
+ repository.create_file(user, 'LICENSE',
Licensee::License.new('mit').content,
- message: 'Add LICENSE', branch_name: 'markdown', update: false)
+ message: 'Add LICENSE', branch_name: 'markdown')
expect(repository.license_blob).to be_nil
end
it 'detects license file with no recognizable open-source license content' do
- repository.commit_file(user, 'LICENSE', 'Copyright!',
- message: 'Add LICENSE', branch_name: 'master', update: false)
+ repository.create_file(user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master')
expect(repository.license_blob.name).to eq('LICENSE')
end
%w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
it "detects '#{filename}'" do
- repository.commit_file(user, filename,
+ repository.create_file(user, filename,
Licensee::License.new('mit').content,
- message: "Add #{filename}", branch_name: 'master', update: false)
+ message: "Add #{filename}", branch_name: 'master')
expect(repository.license_blob.name).to eq(filename)
end
@@ -632,7 +632,7 @@ describe Repository, models: true do
describe '#license_key', caching: true do
before do
- repository.remove_file(user, 'LICENSE',
+ repository.delete_file(user, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
end
@@ -647,16 +647,16 @@ describe Repository, models: true do
end
it 'detects license file with no recognizable open-source license content' do
- repository.commit_file(user, 'LICENSE', 'Copyright!',
- message: 'Add LICENSE', branch_name: 'master', update: false)
+ repository.create_file(user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master')
expect(repository.license_key).to be_nil
end
it 'returns the license key' do
- repository.commit_file(user, 'LICENSE',
+ repository.create_file(user, 'LICENSE',
Licensee::License.new('mit').content,
- message: 'Add LICENSE', branch_name: 'master', update: false)
+ message: 'Add LICENSE', branch_name: 'master')
expect(repository.license_key).to eq('mit')
end
@@ -913,10 +913,9 @@ describe Repository, models: true do
expect(empty_repository).to receive(:expire_emptiness_caches)
expect(empty_repository).to receive(:expire_branches_cache)
- empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+ empty_repository.create_file(user, 'CHANGELOG', 'Changelog!',
message: 'Updates file content',
- branch_name: 'master',
- update: false)
+ branch_name: 'master')
end
end
end
@@ -1796,7 +1795,7 @@ describe Repository, models: true do
describe '#gitlab_ci_yml_for' do
before do
- repository.commit_file(User.last, '.gitlab-ci.yml', 'CONTENT', message: 'Add .gitlab-ci.yml', branch_name: 'master', update: false)
+ repository.create_file(User.last, '.gitlab-ci.yml', 'CONTENT', message: 'Add .gitlab-ci.yml', branch_name: 'master')
end
context 'when there is a .gitlab-ci.yml at the commit' do
@@ -1814,7 +1813,7 @@ describe Repository, models: true do
describe '#route_map_for' do
before do
- repository.commit_file(User.last, '.gitlab/route-map.yml', 'CONTENT', message: 'Add .gitlab/route-map.yml', branch_name: 'master', update: false)
+ repository.create_file(User.last, '.gitlab/route-map.yml', 'CONTENT', message: 'Add .gitlab/route-map.yml', branch_name: 'master')
end
context 'when there is a .gitlab/route-map.yml at the commit' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b69fd24c586..6356f8b6c92 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -22,7 +22,7 @@ describe User, models: true do
it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
- it { is_expected.to have_many(:issues).dependent(:destroy) }
+ it { is_expected.to have_many(:issues).dependent(:restrict_with_exception) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:assigned_issues).dependent(:nullify) }
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
@@ -208,6 +208,22 @@ describe User, models: true do
end
end
end
+
+ describe 'ghost users' do
+ it 'does not allow a non-blocked ghost user' do
+ user = build(:user, :ghost)
+ user.state = 'active'
+
+ expect(user).to be_invalid
+ end
+
+ it 'allows a blocked ghost user' do
+ user = build(:user, :ghost)
+ user.state = 'blocked'
+
+ expect(user).to be_valid
+ end
+ end
end
describe "scopes" do
@@ -1490,4 +1506,41 @@ describe User, models: true do
expect(user.admin).to be true
end
end
+
+ describe '.ghost' do
+ it "creates a ghost user if one isn't already present" do
+ ghost = User.ghost
+
+ expect(ghost).to be_ghost
+ expect(ghost).to be_persisted
+ end
+
+ it "does not create a second ghost user if one is already present" do
+ expect do
+ User.ghost
+ User.ghost
+ end.to change { User.count }.by(1)
+ expect(User.ghost).to eq(User.ghost)
+ end
+
+ context "when a regular user exists with the username 'ghost'" do
+ it "creates a ghost user with a non-conflicting username" do
+ create(:user, username: 'ghost')
+ ghost = User.ghost
+
+ expect(ghost).to be_persisted
+ expect(ghost.username).to eq('ghost1')
+ end
+ end
+
+ context "when a regular user exists with the email 'ghost@example.com'" do
+ it "creates a ghost user with a non-conflicting email" do
+ create(:user, email: 'ghost@example.com')
+ ghost = User.ghost
+
+ expect(ghost).to be_persisted
+ expect(ghost.email).to eq('ghost1@example.com')
+ end
+ end
+ end
end
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
new file mode 100644
index 00000000000..d5761390d39
--- /dev/null
+++ b/spec/policies/user_policy_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe UserPolicy, models: true do
+ let(:current_user) { create(:user) }
+ let(:user) { create(:user) }
+
+ subject { described_class.abilities(current_user, user).to_set }
+
+ describe "reading a user's information" do
+ it { is_expected.to include(:read_user) }
+ end
+
+ describe "destroying a user" do
+ context "when a regular user tries to destroy another regular user" do
+ it { is_expected.not_to include(:destroy_user) }
+ end
+
+ context "when a regular user tries to destroy themselves" do
+ let(:current_user) { user }
+
+ it { is_expected.to include(:destroy_user) }
+ end
+
+ context "when an admin user tries to destroy a regular user" do
+ let(:current_user) { create(:user, :admin) }
+
+ it { is_expected.to include(:destroy_user) }
+ end
+
+ context "when an admin user tries to destroy a ghost user" do
+ let(:current_user) { create(:user, :admin) }
+ let(:user) { create(:user, :ghost) }
+
+ it { is_expected.not_to include(:destroy_user) }
+ end
+ end
+end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 8b3dfedc5a9..5190fcca2d1 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -148,7 +148,7 @@ describe API::Commits, api: true do
end
context 'with project path in URL' do
- let(:url) { "/projects/#{project.namespace.path}%2F#{project.path}/repository/commits" }
+ let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
it 'a new file in project repo' do
post api(url, user), valid_c_params
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index a8ce0430401..29d67b5259e 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -127,7 +127,7 @@ describe API::Files, api: true do
end
it "returns a 400 if editor fails to create file" do
- allow_any_instance_of(Repository).to receive(:commit_file).
+ allow_any_instance_of(Repository).to receive(:create_file).
and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params
@@ -215,7 +215,7 @@ describe API::Files, api: true do
end
it "returns a 400 if fails to create file" do
- allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
+ allow_any_instance_of(Repository).to receive(:delete_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 9c3a92bedbd..fb3dc1b074e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -150,20 +150,10 @@ describe API::Groups, api: true do
expect(response_groups).to eq([group1.name, group3.name])
end
end
- end
-
- describe 'GET /groups/owned' do
- context 'when unauthenticated' do
- it 'returns authentication error' do
- get api('/groups/owned')
-
- expect(response).to have_http_status(401)
- end
- end
- context 'when authenticated as group owner' do
+ context 'when using owned in the request' do
it 'returns an array of groups the user owns' do
- get api('/groups/owned', user2)
+ get api('/groups', user2), owned: true
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -519,7 +509,7 @@ describe API::Groups, api: true do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) }
- let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
+ let(:project_path) { project.full_path.gsub('/', '%2F') }
before(:each) do
allow_any_instance_of(Projects::TransferService).
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 31166b50033..127498ed109 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -173,11 +173,11 @@ describe API::Members, api: true do
expect(response).to have_http_status(400)
end
- it 'returns 422 when access_level is not valid' do
+ it 'returns 400 when access_level is not valid' do
post api("/#{source_type.pluralize}/#{source.id}/members", master),
user_id: stranger.id, access_level: 1234
- expect(response).to have_http_status(422)
+ expect(response).to have_http_status(400)
end
end
end
@@ -230,11 +230,11 @@ describe API::Members, api: true do
expect(response).to have_http_status(400)
end
- it 'returns 422 when access level is not valid' do
+ it 'returns 400 when access level is not valid' do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
access_level: 1234
- expect(response).to have_http_status(422)
+ expect(response).to have_http_status(400)
end
end
end
@@ -342,7 +342,7 @@ describe API::Members, api: true do
post api("/projects/#{project.id}/members", master),
user_id: stranger.id, access_level: Member::OWNER
- expect(response).to have_http_status(422)
+ expect(response).to have_http_status(400)
end.to change { project.members.count }.by(0)
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
new file mode 100644
index 00000000000..73e82647ca0
--- /dev/null
+++ b/spec/requests/api/runner_spec.rb
@@ -0,0 +1,148 @@
+require 'spec_helper'
+
+describe API::Runner do
+ include ApiHelpers
+ include StubGitlabCalls
+
+ let(:registration_token) { 'abcdefg123456' }
+
+ before do
+ stub_gitlab_calls
+ stub_application_setting(runners_registration_token: registration_token)
+ end
+
+ describe '/api/v4/runners' do
+ describe 'POST /api/v4/runners' do
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ post api('/runners')
+ expect(response).to have_http_status 400
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ post api('/runners'), token: 'invalid'
+ expect(response).to have_http_status 403
+ end
+ end
+
+ context 'when valid token is provided' do
+ it 'creates runner with default values' do
+ post api('/runners'), token: registration_token
+
+ runner = Ci::Runner.first
+
+ expect(response).to have_http_status 201
+ expect(json_response['id']).to eq(runner.id)
+ expect(json_response['token']).to eq(runner.token)
+ expect(runner.run_untagged).to be true
+ end
+
+ context 'when project token is used' do
+ let(:project) { create(:empty_project) }
+
+ it 'creates runner' do
+ post api('/runners'), token: project.runners_token
+
+ expect(response).to have_http_status 201
+ expect(project.runners.size).to eq(1)
+ end
+ end
+ end
+
+ context 'when runner description is provided' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ description: 'server.hostname'
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.description).to eq('server.hostname')
+ end
+ end
+
+ context 'when runner tags are provided' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ tag_list: 'tag1, tag2'
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
+ end
+ end
+
+ context 'when option for running untagged jobs is provided' do
+ context 'when tags are provided' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ run_untagged: false,
+ tag_list: ['tag']
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.run_untagged).to be false
+ expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
+ end
+ end
+
+ context 'when tags are not provided' do
+ it 'returns 404 error' do
+ post api('/runners'), token: registration_token,
+ run_untagged: false
+
+ expect(response).to have_http_status 404
+ end
+ end
+ end
+
+ context 'when option for locking Runner is provided' do
+ it 'creates runner' do
+ post api('/runners'), token: registration_token,
+ locked: true
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.locked).to be true
+ end
+ end
+
+ %w(name version revision platform architecture).each do |param|
+ context "when info parameter '#{param}' info is present" do
+ let(:value) { "#{param}_value" }
+
+ it %q(updates provided Runner's parameter) do
+ post api('/runners'), token: registration_token,
+ info: { param => value }
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /api/v4/runners' do
+ context 'when no token is provided' do
+ it 'returns 400 error' do
+ delete api('/runners')
+ expect(response).to have_http_status 400
+ end
+ end
+
+ context 'when invalid token is provided' do
+ it 'returns 403 error' do
+ delete api('/runners'), token: 'invalid'
+ expect(response).to have_http_status 403
+ end
+ end
+
+ context 'when valid token is provided' do
+ let(:runner) { create(:ci_runner) }
+
+ it 'deletes Runner' do
+ delete api('/runners'), token: runner.token
+ expect(response).to have_http_status 200
+ expect(Ci::Runner.count).to eq(0)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 2d7584c3e59..e298ef055e1 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -148,7 +148,7 @@ describe API::V3::Commits, api: true do
end
context 'with project path in URL' do
- let(:url) { "/projects/#{project.namespace.path}%2F#{project.path}/repository/commits" }
+ let(:url) { "/projects/#{project.full_path.gsub('/', '%2F')}/repository/commits" }
it 'a new file in project repo' do
post v3_api(url, user), valid_c_params
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 4af05605ec6..52fd908af7d 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -127,7 +127,7 @@ describe API::V3::Files, api: true do
end
it "returns a 400 if editor fails to create file" do
- allow_any_instance_of(Repository).to receive(:commit_file).
+ allow_any_instance_of(Repository).to receive(:create_file).
and_return(false)
post v3_api("/projects/#{project.id}/repository/files", user), valid_params
@@ -215,7 +215,7 @@ describe API::V3::Files, api: true do
end
it "returns a 400 if fails to create file" do
- allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
+ allow_any_instance_of(Repository).to receive(:delete_file).and_return(false)
delete v3_api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
new file mode 100644
index 00000000000..8b29ad03737
--- /dev/null
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe API::V3::Groups, api: true do
+ include ApiHelpers
+ include UploadHelpers
+
+ let(:user2) { create(:user) }
+ let!(:group2) { create(:group, :private) }
+ let!(:project2) { create(:empty_project, namespace: group2) }
+
+ before do
+ group2.add_owner(user2)
+ end
+
+ describe 'GET /groups/owned' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get v3_api('/groups/owned')
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated as group owner' do
+ it 'returns an array of groups the user owns' do
+ get v3_api('/groups/owned', user2)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(group2.name)
+ end
+ end
+ end
+end
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index 32c2ed8cae7..98c560ffb26 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -5,6 +5,7 @@ describe Groups::DestroyService, services: true do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
+ let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" }
@@ -20,6 +21,7 @@ describe Groups::DestroyService, services: true do
end
it { expect(Group.unscoped.all).not_to include(group) }
+ it { expect(Group.unscoped.all).not_to include(nested_group) }
it { expect(Project.unscoped.all).not_to include(project) }
end
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index c3b468ac47f..d33535d22af 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -66,13 +66,12 @@ describe MergeRequests::ResolveService do
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
- project.repository.commit_file(
+ project.repository.create_file(
user,
'new-file-in-target',
'',
message: 'Add new file in target',
- branch_name: 'conflict-start',
- update: false)
+ branch_name: 'conflict-start')
end
before do
diff --git a/spec/services/users/destroy_spec.rb b/spec/services/users/destroy_spec.rb
index c0bf27c698c..922e82445d0 100644
--- a/spec/services/users/destroy_spec.rb
+++ b/spec/services/users/destroy_spec.rb
@@ -24,6 +24,54 @@ describe Users::DestroyService, services: true do
end
end
+ context "a deleted user's issues" do
+ let(:project) { create :project }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context "for an issue the user has created" do
+ let!(:issue) { create(:issue, project: project, author: user) }
+
+ before do
+ service.execute(user)
+ end
+
+ it 'does not delete the issue' do
+ expect(Issue.find_by_id(issue.id)).to be_present
+ end
+
+ it 'migrates the issue so that the "Ghost User" is the issue owner' do
+ migrated_issue = Issue.find_by_id(issue.id)
+
+ expect(migrated_issue.author).to eq(User.ghost)
+ end
+
+ it 'blocks the user before migrating issues to the "Ghost User' do
+ expect(user).to be_blocked
+ end
+ end
+
+ context "for an issue the user was assigned to" do
+ let!(:issue) { create(:issue, project: project, assignee: user) }
+
+ before do
+ service.execute(user)
+ end
+
+ it 'does not delete issues the user is assigned to' do
+ expect(Issue.find_by_id(issue.id)).to be_present
+ end
+
+ it 'migrates the issue so that it is "Unassigned"' do
+ migrated_issue = Issue.find_by_id(issue.id)
+
+ expect(migrated_issue.assignee).to be_nil
+ end
+ end
+ end
+
context "solo owned groups present" do
let(:solo_owned) { create(:group) }
let(:member) { create(:group_member) }
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 6ed55289ed9..c864a705ca4 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -9,14 +9,7 @@ module CycleAnalyticsHelpers
commit_shas = Array.new(count) do |index|
filename = random_git_name
- options = {
- committer: project.repository.user_to_committer(user),
- author: project.repository.user_to_committer(user),
- commit: { message: message, branch: branch_name, update_ref: true },
- file: { content: "content", path: filename, update: false }
- }
-
- commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
+ commit_sha = project.repository.create_file(user, filename, "content", message: message, branch_name: branch_name)
project.repository.commit(commit_sha)
commit_sha
@@ -35,13 +28,12 @@ module CycleAnalyticsHelpers
project.repository.add_branch(user, source_branch, 'master')
end
- sha = project.repository.commit_file(
+ sha = project.repository.create_file(
user,
random_git_name,
'content',
message: 'commit message',
- branch_name: source_branch,
- update: false)
+ branch_name: source_branch)
project.repository.commit(sha)
opts = {
diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb
new file mode 100644
index 00000000000..984ec7d2741
--- /dev/null
+++ b/spec/support/dropzone_helper.rb
@@ -0,0 +1,37 @@
+module DropzoneHelper
+ # Provides a way to perform `attach_file` for a Dropzone-based file input
+ #
+ # This is accomplished by creating a standard HTML file input on the page,
+ # performing `attach_file` on that field, and then triggering the appropriate
+ # Dropzone events to perform the actual upload.
+ #
+ # This method waits for the upload to complete before returning.
+ def dropzone_file(file_path)
+ # Generate a fake file input that Capybara can attach to
+ page.execute_script <<-JS.strip_heredoc
+ var fakeFileInput = window.$('<input/>').attr(
+ {id: 'fakeFileInput', type: 'file'}
+ ).appendTo('body');
+
+ window._dropzoneComplete = false;
+ JS
+
+ # Attach the file to the fake input selector with Capybara
+ attach_file('fakeFileInput', file_path)
+
+ # Manually trigger a Dropzone "drop" event with the fake input's file list
+ page.execute_script <<-JS.strip_heredoc
+ var fileList = [$('#fakeFileInput')[0].files[0]];
+ var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
+
+ var dropzone = $('.div-dropzone')[0].dropzone;
+ dropzone.on('queuecomplete', function() {
+ window._dropzoneComplete = true;
+ });
+ dropzone.listeners[0].events.drop(e);
+ JS
+
+ # Wait until Dropzone's fired `queuecomplete`
+ loop until page.evaluate_script('window._dropzoneComplete === true')
+ end
+end
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index 4644c7a6b86..4c0f556e736 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -22,7 +22,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
if action
get action
else
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace, project_id: project
end
meta_data = assigns(:issuable_meta_data)
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index a79386b5db9..dea0015f105 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -79,8 +79,8 @@ class MarkdownFeature
def xproject
@xproject ||= begin
- namespace = create(:namespace, name: 'cross-reference')
- create(:project, namespace: namespace) do |project|
+ group = create(:group, :nested)
+ create(:project, namespace: group) do |project|
project.team << [user, :developer]
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4e63a4cd537..c3aa3ef44c2 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -135,7 +135,7 @@ module TestEnv
def copy_repo(project)
base_repo_path = File.expand_path(factory_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git")
+ target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
@@ -152,7 +152,7 @@ module TestEnv
def copy_forked_repo_with_submodules(project)
base_repo_path = File.expand_path(forked_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git")
+ target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
diff --git a/spec/support/update_invalid_issuable.rb b/spec/support/update_invalid_issuable.rb
new file mode 100644
index 00000000000..365c34448ac
--- /dev/null
+++ b/spec/support/update_invalid_issuable.rb
@@ -0,0 +1,57 @@
+shared_examples 'update invalid issuable' do |klass|
+ let(:params) do
+ {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: issuable.iid
+ }
+ end
+
+ let(:issuable) do
+ klass == Issue ? issue : merge_request
+ end
+
+ before do
+ if klass == Issue
+ params.merge!(issue: { title: "any" })
+ else
+ params.merge!(merge_request: { title: "any" })
+ end
+ end
+
+ context 'when updating causes conflicts' do
+ before do
+ allow_any_instance_of(issuable.class).to receive(:save).
+ and_raise(ActiveRecord::StaleObjectError.new(issuable, :save))
+ end
+
+ it 'renders edit when format is html' do
+ put :update, params
+
+ expect(response).to render_template(:edit)
+ expect(assigns[:conflict]).to be_truthy
+ end
+
+ it 'renders json error message when format is json' do
+ params[:format] = "json"
+
+ put :update, params
+
+ expect(response.status).to eq(409)
+ expect(JSON.parse(response.body)).to have_key('errors')
+ end
+ end
+
+ context 'when updating an invalid issuable' do
+ before do
+ key = klass == Issue ? :issue : :merge_request
+ params[key][:title] = ""
+ end
+
+ it 'renders edit when merge request is invalid' do
+ put :update, params
+
+ expect(response).to render_template(:edit)
+ end
+ end
+end
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index 6098be5cd45..ea714fb08f0 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -1,18 +1,17 @@
require 'spec_helper'
describe AttachmentUploader do
- let(:issue) { build(:issue) }
- subject { described_class.new(issue) }
+ let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is true' do
- expect(subject.move_to_cache).to eq(true)
+ expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
- expect(subject.move_to_store).to eq(true)
+ expect(uploader.move_to_store).to eq(true)
end
end
end
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 76f5a4b42ed..c4d558805ab 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -1,18 +1,17 @@
require 'spec_helper'
describe AvatarUploader do
- let(:user) { build(:user) }
- subject { described_class.new(user) }
+ let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is false' do
- expect(subject.move_to_cache).to eq(false)
+ expect(uploader.move_to_cache).to eq(false)
end
end
describe '#move_to_store' do
it 'is false' do
- expect(subject.move_to_store).to eq(false)
+ expect(uploader.move_to_store).to eq(false)
end
end
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 6a712e33c96..b0f5be55c33 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -1,57 +1,35 @@
require 'spec_helper'
describe FileUploader do
- let(:project) { create(:project) }
+ let(:uploader) { described_class.new(build_stubbed(:project)) }
- before do
- @previous_enable_processing = FileUploader.enable_processing
- FileUploader.enable_processing = false
- @uploader = FileUploader.new(project)
- end
-
- after do
- FileUploader.enable_processing = @previous_enable_processing
- @uploader.remove!
- end
+ describe 'initialize' do
+ it 'generates a secret if none is provided' do
+ expect(SecureRandom).to receive(:hex).and_return('secret')
- describe '#image_or_video?' do
- context 'given an image file' do
- before do
- @uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')))
- end
+ uploader = described_class.new(double)
- it 'detects an image based on file extension' do
- expect(@uploader.image_or_video?).to be true
- end
+ expect(uploader.secret).to eq 'secret'
end
- context 'given an video file' do
- before do
- video_file = fixture_file_upload(Rails.root.join('spec', 'fixtures', 'video_sample.mp4'))
- @uploader.store!(video_file)
- end
-
- it 'detects a video based on file extension' do
- expect(@uploader.image_or_video?).to be true
- end
- end
+ it 'accepts a secret parameter' do
+ expect(SecureRandom).not_to receive(:hex)
- it 'does not return image_or_video? for other types' do
- @uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'doc_sample.txt')))
+ uploader = described_class.new(double, 'secret')
- expect(@uploader.image_or_video?).to be false
+ expect(uploader.secret).to eq 'secret'
end
end
describe '#move_to_cache' do
it 'is true' do
- expect(@uploader.move_to_cache).to eq(true)
+ expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
- expect(@uploader.move_to_store).to eq(true)
+ expect(uploader.move_to_store).to eq(true)
end
end
end
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
new file mode 100644
index 00000000000..e9efd13b9aa
--- /dev/null
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+describe UploaderHelper do
+ class ExampleUploader < CarrierWave::Uploader::Base
+ include UploaderHelper
+
+ storage :file
+ end
+
+ def upload_fixture(filename)
+ fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image_or_video?' do
+ let(:uploader) { ExampleUploader.new }
+
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'it returns true for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ uploader.store!(upload_fixture('doc_sample.txt'))
+
+ expect(uploader).not_to be_image_or_video
+ end
+ end
+end
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index b6f6e7b7a2b..ec78ac30593 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -209,6 +209,10 @@ describe 'projects/builds/show', :view do
it 'does not show retry button' do
expect(rendered).not_to have_link('Retry')
end
+
+ it 'does not show New issue button' do
+ expect(rendered).not_to have_link('New issue')
+ end
end
context 'when job is not running' do
@@ -220,6 +224,23 @@ describe 'projects/builds/show', :view do
it 'shows retry button' do
expect(rendered).to have_link('Retry')
end
+
+ context 'if build passed' do
+ it 'does not show New issue button' do
+ expect(rendered).not_to have_link('New issue')
+ end
+ end
+
+ context 'if build failed' do
+ before do
+ build.status = 'failed'
+ render
+ end
+
+ it 'shows New issue button' do
+ expect(rendered).to have_link('New issue')
+ end
+ end
end
describe 'commit title in sidebar' do
@@ -248,4 +269,25 @@ describe 'projects/builds/show', :view do
expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
end
end
+
+ describe 'New issue button' do
+ before do
+ build.status = 'failed'
+ render
+ end
+
+ it 'links to issues/new with the title and description filled in' do
+ title = "Build Failed ##{build.id}"
+ build_url = namespace_project_build_url(project.namespace, project, build)
+ href = new_namespace_project_issue_path(
+ project.namespace,
+ project,
+ issue: {
+ title: title,
+ description: build_url
+ }
+ )
+ expect(rendered).to have_link('New issue', href: href)
+ end
+ end
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 60605460adb..87521ae408e 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -15,24 +15,24 @@ describe RepositoryForkWorker do
it "creates a new repository from a fork" do
expect(shell).to receive(:fork_repository).with(
'/test/path',
- project.path_with_namespace,
+ project.full_path,
project.repository_storage_path,
- fork_project.namespace.path
+ fork_project.namespace.full_path
).and_return(true)
subject.perform(
project.id,
'/test/path',
- project.path_with_namespace,
- fork_project.namespace.path)
+ project.full_path,
+ fork_project.namespace.full_path)
end
it 'flushes various caches' do
expect(shell).to receive(:fork_repository).with(
'/test/path',
- project.path_with_namespace,
+ project.full_path,
project.repository_storage_path,
- fork_project.namespace.path
+ fork_project.namespace.full_path
).and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
@@ -41,8 +41,8 @@ describe RepositoryForkWorker do
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
- subject.perform(project.id, '/test/path', project.path_with_namespace,
- fork_project.namespace.path)
+ subject.perform(project.id, '/test/path', project.full_path,
+ fork_project.namespace.full_path)
end
it "handles bad fork" do
@@ -53,8 +53,8 @@ describe RepositoryForkWorker do
subject.perform(
project.id,
'/test/path',
- project.path_with_namespace,
- fork_project.namespace.path)
+ project.full_path,
+ fork_project.namespace.full_path)
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 1eaa04e21c1..cb4ef36119f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4410,9 +4410,9 @@ vue-resource@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d"
-vue@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.0.3.tgz#3f7698f83d6ad1f0e35955447901672876c63fde"
+vue@^2.1.10:
+ version "2.1.10"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.1.10.tgz#c9235ca48c7925137be5807832ac4e3ac180427b"
watchpack@^1.2.0:
version "1.2.1"