summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-05-09 20:21:02 +0300
committerKamil Trzcinski <ayufan@ayufan.eu>2016-05-09 20:21:02 +0300
commit71da2e0b56a89fc87f2da3685bdebb333690593a (patch)
treebe6418658f118746ab158be162622fe8cb5f0b0e
parent011a905a821e2ff0cd2d9885ef93764018eb8346 (diff)
parent44501820152083d231459223fe09b9d9641b7c1e (diff)
downloadgitlab-ce-71da2e0b56a89fc87f2da3685bdebb333690593a.tar.gz
Merge remote-tracking branch 'origin/master' into docker-registry
-rw-r--r--.rubocop.yml3
-rw-r--r--CHANGELOG86
-rw-r--r--CONTRIBUTING.md13
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock53
-rw-r--r--README.md8
-rw-r--r--app/assets/javascripts/awards_handler.coffee170
-rw-r--r--app/assets/javascripts/labels_select.js.coffee40
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee17
-rw-r--r--app/assets/javascripts/notes.js.coffee8
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss44
-rw-r--r--app/assets/stylesheets/framework/buttons.scss9
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/mobile.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss22
-rw-r--r--app/assets/stylesheets/framework/nav.scss61
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss13
-rw-r--r--app/assets/stylesheets/framework/tables.scss4
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss11
-rw-r--r--app/assets/stylesheets/pages/builds.scss9
-rw-r--r--app/assets/stylesheets/pages/commit.scss26
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss2
-rw-r--r--app/assets/stylesheets/pages/help.scss19
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/milestone.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss9
-rw-r--r--app/assets/stylesheets/pages/profile.scss18
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/assets/stylesheets/pages/status.scss2
-rw-r--r--app/assets/stylesheets/pages/todos.scss9
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/admin/application_controller.rb8
-rw-r--r--app/controllers/admin/hooks_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_controller.rb38
-rw-r--r--app/controllers/admin/impersonations_controller.rb26
-rw-r--r--app/controllers/admin/users_controller.rb18
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/graphs_controller.rb4
-rw-r--r--app/controllers/projects/hooks_controller.rb14
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb4
-rw-r--r--app/controllers/registrations_controller.rb7
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb48
-rw-r--r--app/helpers/nav_helper.rb13
-rw-r--r--app/helpers/projects_helper.rb14
-rw-r--r--app/mailers/emails/notes.rb8
-rw-r--r--app/models/appearance.rb13
-rw-r--r--app/models/application_setting.rb16
-rw-r--r--app/models/audit_event.rb4
-rw-r--r--app/models/broadcast_message.rb4
-rw-r--r--app/models/ci/build.rb14
-rw-r--r--app/models/ci/commit.rb10
-rw-r--r--app/models/ci/runner.rb14
-rw-r--r--app/models/ci/trigger.rb2
-rw-r--r--app/models/ci/variable.rb6
-rw-r--r--app/models/commit_status.rb17
-rw-r--r--app/models/concerns/issuable.rb5
-rw-r--r--app/models/deploy_key.rb6
-rw-r--r--app/models/email.rb2
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/generic_commit_status.rb17
-rw-r--r--app/models/group.rb21
-rw-r--r--app/models/hooks/project_hook.rb2
-rw-r--r--app/models/hooks/service_hook.rb2
-rw-r--r--app/models/hooks/system_hook.rb2
-rw-r--r--app/models/hooks/web_hook.rb25
-rw-r--r--app/models/identity.rb4
-rw-r--r--app/models/issue.rb9
-rw-r--r--app/models/key.rb6
-rw-r--r--app/models/label.rb16
-rw-r--r--app/models/label_link.rb2
-rw-r--r--app/models/lfs_object.rb4
-rw-r--r--app/models/member.rb8
-rw-r--r--app/models/members/group_member.rb8
-rw-r--r--app/models/members/project_member.rb8
-rw-r--r--app/models/merge_request.rb13
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/namespace.rb20
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/notification_setting.rb13
-rw-r--r--app/models/personal_snippet.rb6
-rw-r--r--app/models/project.rb100
-rw-r--r--app/models/project_group_link.rb12
-rw-r--r--app/models/project_import_data.rb9
-rw-r--r--app/models/project_services/asana_service.rb13
-rw-r--r--app/models/project_services/assembla_service.rb13
-rw-r--r--app/models/project_services/bamboo_service.rb13
-rw-r--r--app/models/project_services/buildkite_service.rb17
-rw-r--r--app/models/project_services/builds_email_service.rb13
-rw-r--r--app/models/project_services/campfire_service.rb13
-rw-r--r--app/models/project_services/ci_service.rb13
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb13
-rw-r--r--app/models/project_services/drone_ci_service.rb13
-rw-r--r--app/models/project_services/emails_on_push_service.rb13
-rw-r--r--app/models/project_services/external_wiki_service.rb13
-rw-r--r--app/models/project_services/flowdock_service.rb13
-rw-r--r--app/models/project_services/gemnasium_service.rb13
-rw-r--r--app/models/project_services/gitlab_ci_service.rb13
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb13
-rw-r--r--app/models/project_services/hipchat_service.rb13
-rw-r--r--app/models/project_services/irker_service.rb13
-rw-r--r--app/models/project_services/issue_tracker_service.rb15
-rw-r--r--app/models/project_services/jira_service.rb15
-rw-r--r--app/models/project_services/pivotaltracker_service.rb13
-rw-r--r--app/models/project_services/pushover_service.rb13
-rw-r--r--app/models/project_services/redmine_service.rb13
-rw-r--r--app/models/project_services/slack_service.rb15
-rw-r--r--app/models/project_services/teamcity_service.rb13
-rw-r--r--app/models/project_snippet.rb8
-rw-r--r--app/models/protected_branch.rb2
-rw-r--r--app/models/release.rb2
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/security_event.rb4
-rw-r--r--app/models/sent_notification.rb8
-rw-r--r--app/models/service.rb13
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/models/spam_log.rb17
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/models/user.rb51
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/create_tag_service.rb44
-rw-r--r--app/services/git_push_service.rb1
-rw-r--r--app/services/git_tag_push_service.rb1
-rw-r--r--app/services/issues/move_service.rb15
-rw-r--r--app/services/merge_requests/build_service.rb39
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/projects/destroy_service.rb4
-rw-r--r--app/services/wiki_pages/create_service.rb3
-rw-r--r--app/views/admin/builds/_build.html.haml26
-rw-r--r--app/views/admin/builds/index.html.haml3
-rw-r--r--app/views/admin/hooks/index.html.haml10
-rw-r--r--app/views/admin/logs/show.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/devise/shared/_signup_box.html.haml4
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml2
-rw-r--r--app/views/groups/activity.html.haml1
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml1
-rw-r--r--app/views/groups/issues.html.haml3
-rw-r--r--app/views/groups/merge_requests.html.haml3
-rw-r--r--app/views/groups/milestones/index.html.haml3
-rw-r--r--app/views/groups/projects.html.haml1
-rw-r--r--app/views/groups/show.html.haml34
-rw-r--r--app/views/help/ui.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/group_settings.html.haml3
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml20
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml40
-rw-r--r--app/views/notify/note_snippet_email.html.haml1
-rw-r--r--app/views/notify/note_snippet_email.text.erb8
-rw-r--r--app/views/profiles/emails/index.html.haml2
-rw-r--r--app/views/profiles/show.html.haml4
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/_readme.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/builds/index.html.haml4
-rw-r--r--app/views/projects/builds/show.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml9
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml6
-rw-r--r--app/views/projects/commit/_commit_box.html.haml26
-rw-r--r--app/views/projects/commit/branches.html.haml1
-rw-r--r--app/views/projects/commits/_head.html.haml3
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/projects/graphs/ci.html.haml2
-rw-r--r--app/views/projects/graphs/commits.html.haml2
-rw-r--r--app/views/projects/graphs/languages.html.haml2
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/hooks/index.html.haml5
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml10
-rw-r--r--app/views/projects/network/_head.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/issuable/_filter.html.haml4
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/sherlock/file_samples/show.html.haml2
-rw-r--r--app/views/sherlock/queries/show.html.haml2
-rw-r--r--app/views/sherlock/transactions/index.html.haml2
-rw-r--r--app/views/sherlock/transactions/show.html.haml2
-rw-r--r--app/views/users/show.html.haml4
-rw-r--r--app/views/votes/_votes_block.html.haml18
-rw-r--r--app/workers/repository_check/single_repository_worker.rb34
-rwxr-xr-xbin/background_jobs2
-rwxr-xr-xbin/web4
-rw-r--r--config/application.rb25
-rw-r--r--config/gitlab.yml.example1
-rw-r--r--config/initializers/devise_async.rb1
-rw-r--r--config/initializers/metrics.rb37
-rw-r--r--config/initializers/sentry.rb3
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/routes.rb6
-rw-r--r--db/fixtures/development/10_merge_requests.rb5
-rw-r--r--db/migrate/20160413115152_add_token_to_web_hooks.rb5
-rw-r--r--db/schema.rb19
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/notes.md10
-rw-r--r--doc/api/users.md80
-rw-r--r--doc/ci/docker/using_docker_build.md6
-rw-r--r--doc/ci/docker/using_docker_images.md8
-rw-r--r--doc/ci/examples/README.md4
-rw-r--r--doc/ci/examples/php.md6
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md10
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md6
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/services/postgres.md2
-rw-r--r--doc/ci/services/redis.md2
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/performance.md258
-rw-r--r--doc/development/testing.md1
-rw-r--r--doc/install/installation.md56
-rw-r--r--doc/install/requirements.md9
-rw-r--r--doc/integration/github.md4
-rw-r--r--doc/monitoring/performance/gitlab_configuration.md2
-rw-r--r--doc/monitoring/performance/influxdb_configuration.md2
-rw-r--r--doc/monitoring/performance/influxdb_schema.md2
-rw-r--r--doc/update/patch_versions.md4
-rw-r--r--docker/README.md6
-rw-r--r--features/groups.feature4
-rw-r--r--features/steps/group/milestones.rb6
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/commits/tags.rb4
-rw-r--r--features/steps/user.rb4
-rw-r--r--lib/api/commits.rb8
-rw-r--r--lib/api/helpers.rb16
-rw-r--r--lib/api/milestones.rb10
-rw-r--r--lib/api/project_snippets.rb15
-rw-r--r--lib/gitlab/backend/shell.rb18
-rw-r--r--lib/gitlab/bitbucket_import/client.rb2
-rw-r--r--lib/gitlab/git_access.rb4
-rw-r--r--lib/gitlab/github_import/comment_formatter.rb21
-rw-r--r--lib/gitlab/push_data_builder.rb2
-rw-r--r--lib/gitlab/sanitizers/svg.rb37
-rw-r--r--lib/gitlab/sanitizers/svg/whitelist.rb107
-rw-r--r--lib/tasks/auto_annotate_models.rake44
-rw-r--r--lib/tasks/gitlab/db.rake5
-rw-r--r--spec/controllers/admin/impersonation_controller_spec.rb19
-rw-r--r--spec/controllers/admin/impersonations_controller_spec.rb95
-rw-r--r--spec/controllers/admin/users_controller_spec.rb49
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb39
-rw-r--r--spec/factories/project_hooks.rb4
-rw-r--r--spec/features/dashboard/label_filter_spec.rb29
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/login_spec.rb2
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb10
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb83
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb44
-rw-r--r--spec/features/signup_spec.rb24
-rw-r--r--spec/features/users_spec.rb16
-rw-r--r--spec/fixtures/sanitized.svg50
-rw-r--r--spec/fixtures/unsanitized.svg50
-rw-r--r--spec/helpers/blob_helper_spec.rb12
-rw-r--r--spec/helpers/issues_helper_spec.rb36
-rw-r--r--spec/helpers/projects_helper_spec.rb9
-rw-r--r--spec/javascripts/merge_request_widget_spec.js.coffee49
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb26
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb30
-rw-r--r--spec/models/hooks/web_hook_spec.rb46
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb95
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb17
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb81
-rw-r--r--spec/models/project_services/campfire_service_spec.rb42
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb49
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb13
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb17
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb (renamed from spec/models/external_wiki_service_spec.rb)17
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb14
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb16
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb14
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb14
-rw-r--r--spec/models/project_services/irker_service_spec.rb14
-rw-r--r--spec/models/project_services/jira_service_spec.rb26
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb42
-rw-r--r--spec/models/project_services/pushover_service_spec.rb20
-rw-r--r--spec/models/project_services/redmine_service_spec.rb49
-rw-r--r--spec/models/project_services/slack_service_spec.rb17
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb95
-rw-r--r--spec/models/project_spec.rb14
-rw-r--r--spec/models/repository_spec.rb29
-rw-r--r--spec/requests/api/commits_spec.rb35
-rw-r--r--spec/requests/api/milestones_spec.rb31
-rw-r--r--spec/requests/api/notes_spec.rb41
-rw-r--r--spec/requests/api/project_snippets_spec.rb87
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb4
-rw-r--r--spec/services/create_tag_service_spec.rb53
-rw-r--r--spec/services/issues/move_service_spec.rb38
-rw-r--r--spec/services/merge_requests/build_service_spec.rb181
-rw-r--r--spec/services/notification_service_spec.rb65
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/issue_tracker_service_shared_example.rb7
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb33
321 files changed, 3895 insertions, 1356 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 83ed6c38678..9f179efa3ce 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -953,10 +953,9 @@ Performance/DoubleStartEndWith:
Performance/EndWith:
Enabled: false
-# TODO: Enable LstripRstrip Cop.
# Use `strip` instead of `lstrip.rstrip`.
Performance/LstripRstrip:
- Enabled: false
+ Enabled: true
# TODO: Enable RangeInclude Cop.
# Use `Range#cover?` instead of `Range#include?`.
diff --git a/CHANGELOG b/CHANGELOG
index ed151df9e25..fed3caef7e8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,22 +1,48 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
+ - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
+ - Project#open_branches has been cleaned up and no longer loads entire records into memory.
+ - Log to application.log when an admin starts and stops impersonating a user
+ - Updated gitlab_git to 10.1.0
+ - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists
+ - Reduce delay in destroying a project from 1-minute to immediately
- Make build status canceled if any of the jobs was canceled and none failed
+ - Upgrade Sidekiq to 4.1.2
+ - Sanitize repo paths in new project error message
+ - Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph.
+ - Support e-mail notifications for comments on project snippets
- Use ActionDispatch Remote IP for Akismet checking
- Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
+ - Update SVG sanitizer to conform to SVG 1.1
- Updated search UI
- Display informative message when new milestone is created
- - Replace Devise Async with Devise ActiveJob integration. !3902 (Connor Shea)
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- - Backport GitLab Enterprise support from EE
+ - Backport GitHub Enterprise import support from EE
+ - Create tags using Rugged for performance reasons. !3745
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
+ - Added multiple colors for labels in dropdowns when dups happen.
+ - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
+ - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
+ - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
+ - Expire repository exists? and has_visible_content? caches after a push if necessary
+ - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
+
+v 8.7.3
+ - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
+ - Merge request widget displays TeamCity build state and code coverage correctly again.
+ - Fix the line code when importing PR review comments from GitHub. !4010
+ - Wikis are now initialized on legacy projects when checking repositories
v 8.7.2
- The "New Branch" button is now loaded asynchronously
+ - Fix error 500 when trying to create a wiki page
+ - Updated spacing between notification label and button
+ - Label titles in filters are now escaped properly
v 8.7.1
- Throttle the update of `project.last_activity_at` to 1 minute. !3848
@@ -138,13 +164,25 @@ v 8.7.0
- Import GitHub labels
- Add option to filter by "Owned projects" on dashboard page
- Import GitHub milestones
- - Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
- Allow enable/disable push events for system hooks
- Fix GitHub project's link in the import page when provider has a custom URL
- Add RAW build trace output and button on build page
- Add incremental build trace update into CI API
+v 8.6.8
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent XSS via label drop-down
+ - Prevent information disclosure via milestone API
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.6.7
- Fix persistent XSS vulnerability in `commit_person_link` helper
- Fix persistent XSS vulnerability in Label and Milestone dropdowns
@@ -286,6 +324,17 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
+v 8.5.12
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.5.11
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -436,6 +485,17 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
+v 8.4.10
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.4.9
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -561,6 +621,15 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
+v 8.3.9
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
v 8.3.8
- Fix persistent XSS vulnerability in `commit_person_link` helper
@@ -670,6 +739,17 @@ v 8.3.0
- Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
+v 8.2.5
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.2.4
+ - Bump Git version requirement to 2.7.4
+
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 084c2d616a9..9fe4cf7b0f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -142,6 +142,16 @@ code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
+You are encouraged to use the template below for feature proposals.
+
+```
+## Description including problem, use cases, benefits, and/or goals
+
+## Proposal
+
+## Links / references
+```
+
For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
@@ -349,7 +359,7 @@ on your merge request feel free to mention one of the Merge Marshalls in the
Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the
-[Thoughtbot code review guide] into account.
+[code review guidelines](doc/development/code_review.md) into account.
### Merge request description format
@@ -523,4 +533,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
-[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
diff --git a/Gemfile b/Gemfile
index 0301f6fe062..c7db12a1947 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,7 +19,8 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
-gem 'doorkeeper', '~> 2.2.0'
+gem 'doorkeeper', '~> 3.1'
+gem 'devise-async', '~> 0.9.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
@@ -271,7 +272,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
- gem 'rspec-rails', '~> 3.3.0'
+ gem 'rspec-rails', '~> 3.4.0'
gem 'rspec-retry'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
@@ -321,7 +322,7 @@ gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 4.3.0'
-gem "mail_room", "~> 0.6.1"
+gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2b1cfdc9bb2..36efb66665f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -135,7 +135,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.1)
+ concurrent-ruby (1.0.2)
connection_pool (2.2.0)
coveralls (0.8.13)
json (~> 1.8)
@@ -165,6 +165,8 @@ GEM
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
+ devise-async (0.9.0)
+ devise (~> 3.2)
devise-two-factor (2.0.1)
activesupport
attr_encrypted (~> 1.3.2)
@@ -174,7 +176,7 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
- doorkeeper (2.2.2)
+ doorkeeper (3.1.0)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
@@ -352,7 +354,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (10.0.1)
+ gitlab_git (10.1.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -466,7 +468,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mail_room (0.6.1)
+ mail_room (0.7.0)
method_source (0.8.2)
mime-types (2.99.1)
mimemagic (0.3.0)
@@ -663,29 +665,29 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.3.0)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-core (3.3.2)
- rspec-support (~> 3.3.0)
- rspec-expectations (3.3.1)
+ rspec (3.4.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-core (3.4.4)
+ rspec-support (~> 3.4.0)
+ rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-mocks (3.3.2)
+ rspec-support (~> 3.4.0)
+ rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-rails (3.3.3)
+ rspec-support (~> 3.4.0)
+ rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-support (~> 3.3.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-support (~> 3.4.0)
rspec-retry (0.4.5)
rspec-core
- rspec-support (3.3.0)
+ rspec-support (3.4.1)
rubocop (0.38.0)
parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
@@ -739,7 +741,7 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (4.1.1)
+ sidekiq (4.1.2)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
redis (~> 3.2, >= 3.2.1)
@@ -922,9 +924,10 @@ DEPENDENCIES
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.5.4)
+ devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
- doorkeeper (~> 2.2.0)
+ doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
@@ -964,7 +967,7 @@ DEPENDENCIES
letter_opener_web (~> 1.3.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3)
- mail_room (~> 0.6.1)
+ mail_room (~> 0.7)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@@ -1014,7 +1017,7 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
- rspec-rails (~> 3.3.0)
+ rspec-rails (~> 3.4.0)
rspec-retry
rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
@@ -1061,4 +1064,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.11.2
+ 1.12.1
diff --git a/README.md b/README.md
index afa60116ebb..5e41665a6ba 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# GitLab
-[![build status](https://ci.gitlab.com/projects/1/status.svg?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
+[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
@@ -20,6 +20,10 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Completely free and open source (MIT Expat license)
- Powered by [Ruby on Rails](https://github.com/rails/rails)
+## Hiring
+
+We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/).
+
## Editions
There are two editions of GitLab:
@@ -80,7 +84,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
## GitLab release cycle
-For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
+For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md).
## Upgrading
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index fcba9818726..bf95e06b4e5 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,58 +1,58 @@
class @AwardsHandler
- constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) ->
- $(".js-add-award").on "click", (event) =>
+ constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
+ $('.js-add-award').on 'click', (event) =>
event.stopPropagation()
event.preventDefault()
@showEmojiMenu()
- $("html").on 'click', (event) ->
- if !$(event.target).closest(".emoji-menu").length
- if $(".emoji-menu").is(":visible")
- $(".emoji-menu").removeClass "is-visible"
+ $('html').on 'click', (event) ->
+ if !$(event.target).closest('.emoji-menu').length
+ if $('.emoji-menu').is(':visible')
+ $('.emoji-menu').removeClass 'is-visible'
- $(".awards")
- .off "click"
- .on "click", ".js-emoji-btn", @handleClick
+ $('.awards')
+ .off 'click'
+ .on 'click', '.js-emoji-btn', @handleClick
@renderFrequentlyUsedBlock()
handleClick: (e) ->
e.preventDefault()
emoji = $(this)
- .find(".icon")
- .data "emoji"
+ .find('.icon')
+ .data 'emoji'
- if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
- awards_handler.addAward "thumbsdown"
+ if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
+ awardsHandler.addAward 'thumbsdown'
- else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
- awards_handler.addAward "thumbsup"
+ else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
+ awardsHandler.addAward 'thumbsup'
- awards_handler.addAward emoji
+ awardsHandler.addAward emoji
$(this).trigger 'blur'
didUserClickEmoji: (that, emoji) ->
- if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
- $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
+ if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
+ $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
showEmojiMenu: ->
- if $(".emoji-menu").length
- if $(".emoji-menu").is ".is-visible"
- $(".emoji-menu").removeClass "is-visible"
- $("#emoji_search").blur()
+ if $('.emoji-menu').length
+ if $('.emoji-menu').is '.is-visible'
+ $('.emoji-menu').removeClass 'is-visible'
+ $('#emoji_search').blur()
else
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
else
- $('.js-add-award').addClass "is-loading"
- $.get @get_emojis_url, (response) =>
- $('.js-add-award').removeClass "is-loading"
- $(".js-award-holder").append response
+ $('.js-add-award').addClass 'is-loading'
+ $.get @getEmojisUrl, (response) =>
+ $('.js-add-award').removeClass 'is-loading'
+ $('.js-award-holder').append response
setTimeout =>
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
@setupSearch()
, 200
@@ -60,7 +60,7 @@ class @AwardsHandler
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
- $(".emoji-menu").removeClass "is-visible"
+ $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
@@ -69,9 +69,9 @@ class @AwardsHandler
if @isActive(emoji)
@decrementCounter(emoji)
else
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
counter.text(parseInt(counter.text()) + 1)
- counter.parent().addClass("active")
+ counter.parent().addClass('active')
@addMeToAuthorList(emoji)
else
@createEmoji(emoji)
@@ -80,47 +80,47 @@ class @AwardsHandler
@findEmojiIcon(emoji).length > 0
isActive: (emoji) ->
- @findEmojiIcon(emoji).parent().hasClass("active")
+ @findEmojiIcon(emoji).parent().hasClass('active')
decrementCounter: (emoji) ->
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
emojiIcon = counter.parent()
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
- else if emoji == "thumbsup" || emoji == "thumbsdown"
- emojiIcon.tooltip("destroy")
+ else if emoji == 'thumbsup' || emoji == 'thumbsdown'
+ emojiIcon.tooltip('destroy')
counter.text(0)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
else
- emojiIcon.tooltip("destroy")
+ emojiIcon.tooltip('destroy')
emojiIcon.remove()
removeMeFromAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- authors = award_block
- .attr("data-original-title")
- .split(", ")
- authors.splice(authors.indexOf("me"),1)
- award_block
- .closest(".js-emoji-btn")
- .attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ awardBlock = @findEmojiIcon(emoji).parent()
+ authors = awardBlock
+ .attr('data-original-title')
+ .split(', ')
+ authors.splice(authors.indexOf('me'),1)
+ awardBlock
+ .closest('.js-emoji-btn')
+ .attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
addMeToAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- origTitle = award_block.attr("data-original-title").trim()
+ awardBlock = @findEmojiIcon(emoji).parent()
+ origTitle = awardBlock.attr('data-original-title').trim()
authors = []
if origTitle
authors = origTitle.split(', ')
- authors.push("me")
- award_block.attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ authors.push('me')
+ awardBlock.attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
resetTooltip: (award) ->
- award.tooltip("destroy")
+ award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
@@ -139,20 +139,28 @@ class @AwardsHandler
"</button>"
)
- emoji_node = $(nodes.join("\n"))
- .insertBefore(".js-award-holder")
- .find(".emoji-icon")
- .data("emoji", emoji)
+ $(nodes.join("\n"))
+ .insertBefore('.js-award-holder')
+ .find('.emoji-icon')
+ .data('emoji', emoji)
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
- "emoji-#{@unicodes[emoji]}"
+ emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+
+ if emojiIcon.length > 0
+ unicodeName = emojiIcon.data('unicode-name')
+ else
+ # Find by alias
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
+
+ "emoji-#{unicodeName}"
postEmoji: (emoji, callback) ->
- $.post @post_emoji_url, { note: {
+ $.post @postEmojiUrl, { note: {
note: ":#{emoji}:"
- noteable_type: @noteable_type
- noteable_id: @noteable_id
+ noteable_type: @noteableType
+ noteable_id: @noteableId
}},(data) ->
if data.ok
callback.call()
@@ -166,42 +174,42 @@ class @AwardsHandler
}, 200)
addEmojiToFrequentlyUsedList: (emoji) ->
- frequently_used_emojis = @getFrequentlyUsedEmojis()
- frequently_used_emojis.push(emoji)
- $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis.push(emoji)
+ $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: ->
- frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
- _.compact(_.uniq(frequently_used_emojis))
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
+ _.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
- frequently_used_emojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- ul = $("<ul>")
+ ul = $('<ul>')
- for emoji in frequently_used_emojis
+ for emoji in frequentlyUsedEmojis
do (emoji) ->
- $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
+ $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
- $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+ $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: ->
- $("input.emoji-search").keyup (ev) =>
+ $('input.emoji-search').keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
- $("ul.emoji-menu-search, h5.emoji-search").remove()
+ $('ul.emoji-menu-search, h5.emoji-search').remove()
if term
# Generate a search result block
- h5 = $("<h5>").text("Search results").addClass("emoji-search")
- found_emojis = @searchEmojis(term).show()
- ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
- $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
- $(".emoji-menu-content").append(h5).append(ul)
+ h5 = $('<h5>').text('Search results').addClass('emoji-search')
+ foundEmojis = @searchEmojis(term).show()
+ ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
+ $('.emoji-menu-content').append(h5).append(ul)
else
- $(".emoji-menu-content").children().show()
+ $('.emoji-menu-content').children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 7d61cd9bf73..995fd768603 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -163,6 +163,21 @@ class @LabelsSelect
$.ajax(
url: labelUrl
).done (data) ->
+ data = _.chain data
+ .groupBy (label) ->
+ label.title
+ .map (label) ->
+ color = _.map label, (dup) ->
+ dup.color
+
+ return {
+ id: label[0].id
+ title: label[0].title
+ color: color
+ duplicate: color.length > 1
+ }
+ .value()
+
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
@@ -178,6 +193,7 @@ class @LabelsSelect
if data.length > 2
data.splice 2, 0, 'divider'
+
callback data
renderRow: (label) ->
@@ -192,11 +208,31 @@ class @LabelsSelect
if $dropdown.hasClass('js-multiselect') and removesAll
selectedClass.push 'dropdown-clear-active'
- color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
+ if label.duplicate
+ spacing = 100 / label.color.length
+
+ # Reduce the colors to 4
+ label.color = label.color.filter (color, i) ->
+ i < 4
+
+ color = _.map(label.color, (color, i) ->
+ percentFirst = Math.floor(spacing * i)
+ percentSecond = Math.floor(spacing * (i + 1))
+ "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
+ ).join(',')
+ color = "linear-gradient(#{color})"
+ else
+ if label.color?
+ color = label.color[0]
+
+ if color
+ colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
+ else
+ colorEl = ''
"<li>
<a href='#' class='#{selectedClass.join(' ')}'>
- #{color}
+ #{colorEl}
#{_.escape(label.title)}
</a>
</li>"
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 065626beeb8..17a5a057a94 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -68,20 +68,18 @@ class @MergeRequestWidget
$.getJSON @opts.ci_status_url, (data) =>
@readyForCICheck = true
- if @firstCICheck
- @firstCICheck = false
- @opts.ci_status = data.status
-
- if @opts.ci_status is ''
- @opts.ci_status = data.status
+ if data.status is ''
return
- if data.status isnt @opts.ci_status and data.status?
+ if @firstCiCheck || data.status isnt @opts.ci_status and data.status?
+ @opts.ci_status = data.status
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
- if showNotification
+ # The first check should only update the UI, a notification
+ # should only be displayed on status changes
+ if showNotification and not @firstCiCheck
status = @ciLabelForStatus(data.status)
if status is "preparing"
@@ -104,8 +102,7 @@ class @MergeRequestWidget
@close()
Turbolinks.visit _this.opts.builds_path
)
-
- @opts.ci_status = data.status
+ @firstCiCheck = false
showCIStatus: (state) ->
$('.ci_widget').hide()
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 82e210fed7d..efb3e8e2198 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -167,8 +167,8 @@ class @Notes
return
if note.award
- awards_handler.addAwardToEmojiBar(note.note)
- awards_handler.scrollToAwards()
+ awardsHandler.addAwardToEmojiBar(note.note)
+ awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
@@ -373,11 +373,11 @@ class @Notes
new GLForm form
if scrollTo? and myLastNote?
- # scroll to the bottom
+ # scroll to the bottom
# so the open of the last element doesn't make a jump
$('html, body').scrollTop($(document).height());
$('html, body').animate({
- scrollTop: myLastNote.offset().top - 150
+ scrollTop: myLastNote.offset().top - 150
}, 500, ->
$noteText = form.find(".js-note-text")
$noteText.focus()
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c85ab9148d0..560de9fc0bd 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -25,6 +25,7 @@
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
+@import "framework/modal.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
@import "framework/progress.scss";
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 5aa425dab6c..f5ce70b606b 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -28,6 +28,7 @@
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
+ &.s70 { width: 70px; height: 70px; margin-right: 14px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index e72e4aa47ef..434a26d57c6 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -18,7 +18,7 @@
line-height: 36px;
}
-.gray-content-block {
+.row-content-block {
margin-top: 0;
margin-bottom: -$gl-padding;
background-color: $background-color;
@@ -81,6 +81,11 @@
margin-left: 10px;
}
}
+
+ &.build-content {
+ background-color: $white-light;
+ border-top: none;
+ }
}
.cover-block {
@@ -113,7 +118,7 @@
line-height: 1.1;
h1 {
- color: #313236;
+ color: $gl-gray-dark;
margin-bottom: 6px;
font-size: 23px;
}
@@ -150,6 +155,41 @@
right: auto;
}
}
+
+ &.groups-cover-block {
+ background: $white-light;
+ border-bottom: 1px solid $border-color;
+ text-align: left;
+ padding: 24px 0;
+
+ .group-info {
+ .cover-title {
+ margin-top: 9px;
+ }
+
+ p {
+ margin-bottom: 0;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ text-align: center;
+
+ .avatar {
+ float: none;
+ }
+ }
+ }
+
+ .group-info {
+
+ h1 {
+ display: inline;
+ font-weight: normal;
+ font-size: 24px;
+ color: $gl-title-color;
+ }
+ }
}
.block-connector {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 062da397b6b..eaf85bb17ca 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -59,7 +59,7 @@
}
@mixin btn-gray {
- @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
+ @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark);
}
@mixin btn-white {
@@ -251,3 +251,10 @@
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
+
+.btn-build {
+ margin-left: 10px;
+ i {
+ color: $gl-icon-color;
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 5fa10d29a87..97f9d582007 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -30,6 +30,10 @@ header {
border: none;
border-bottom: 1px solid $border-color;
+ &.with-horizontal-nav {
+ border-bottom: none;
+ }
+
.container-fluid {
width: 100% !important;
filter: none;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 7eb451c124e..33cbee85987 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -30,7 +30,7 @@
}
.rss-btn {
- display: none !important;
+ display: none;
}
.project-home-links {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
new file mode 100644
index 00000000000..26ad2870aa0
--- /dev/null
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -0,0 +1,22 @@
+.modal-body {
+ position: relative;
+ overflow-y: auto;
+ padding: 15px;
+
+ .form-actions {
+ margin: -$gl-padding+1;
+ margin-top: 15px;
+ }
+
+ .text-danger {
+ font-weight: bold;
+ }
+}
+
+body.modal-open {
+ overflow: hidden;
+}
+
+.modal .modal-dialog {
+ width: 860px;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 5fe687dcec3..7c18e93a261 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -26,8 +26,8 @@
}
&.active a {
- color: #000;
- border-bottom: 2px solid #4688f1;
+ border-bottom: 2px solid $link-underline-blue;
+ color: $black;
}
.badge {
@@ -140,6 +140,12 @@
}
}
+ .project-filter-form {
+ input {
+ background-color: $background-color;
+ }
+ }
+
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
@@ -187,13 +193,31 @@
}
.layout-nav {
+ position: fixed;
+ top: $header-height;
+ width: 100%;
+ z-index: 1;
background: $background-color;
border-bottom: 1px solid $border-color;
+ transition-duration: .3s;
.controls {
float: right;
- position: relative;
- top: 10px;
+ padding: 7px 5px 0 0;
+
+ i {
+ color: $layout-link-gray;
+ }
+
+ .fa-rss,
+ .fa-cog {
+ font-size: 16px;
+ }
+
+ .fa-caret-down {
+ margin-left: 5px;
+ color: $gl-icon-color;
+ }
.dropdown {
margin-left: 7px;
@@ -202,5 +226,34 @@
.nav-links {
border-bottom: none;
+ height: 51px;
+ white-space: nowrap;
+ overflow-x: auto;
+
+ li {
+
+ a {
+ padding-top: 10px;
+ }
+
+ a, i {
+ color: $layout-link-gray;
+ }
+
+ &.active {
+ a, i {
+ color: $black;
+ }
+ }
+
+ .badge {
+ color: $gl-icon-color;
+ }
+ }
}
+
+}
+
+.page-with-layout-nav {
+ margin-top: 50px;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 18189e985c4..e940fd7286e 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -3,6 +3,7 @@
position: absolute;
width: 58px;
cursor: pointer;
+ margin-top: 8px;
}
.page-with-sidebar {
@@ -62,7 +63,7 @@
float: left;
height: $header-height;
width: 100%;
- padding: 11px 0 11px 22px;
+ padding-left: 22px;
overflow: hidden;
outline: none;
transition-duration: .3s;
@@ -85,7 +86,7 @@
margin: 0;
margin-left: 50px;
font-size: 19px;
- line-height: 41px;
+ line-height: 50px;
font-weight: normal;
}
}
@@ -254,6 +255,10 @@
}
}
}
+
+ .layout-nav {
+ padding-right: $sidebar_collapsed_width;
+ }
}
.page-sidebar-expanded {
@@ -280,6 +285,10 @@
}
}
}
+
+ .layout-nav {
+ padding-right: $sidebar_width;
+ }
}
.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 75b770ae5a2..b42075c98d0 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -32,13 +32,11 @@ table {
th {
background-color: $background-color;
font-weight: normal;
- font-size: 15px;
- border-bottom: 1px solid $border-color;
+ border-bottom: none;
}
td {
border-color: $table-border-color;
- border-bottom: 1px solid $border-color;
}
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 96bab7880c2..6a45c34ccbb 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -81,7 +81,7 @@
// Labels
.label {
- padding: 2px 4px;
+ padding: 4px 5px;
font-size: 13px;
font-style: normal;
font-weight: normal;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index b2535ddf4bd..2779cd56788 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -42,14 +42,14 @@
margin: 24px 0 12px;
padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed;
- color: #313236;
+ color: $gl-gray-dark;
}
h2 {
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px;
- color: #313236;
+ color: $gl-gray-dark;
}
h3 {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b8ed7e8a74c..ccb4e5381b7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -12,7 +12,7 @@ $gutter_inner_width: 258px;
*/
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
-$table-border-color: #eef0f2;
+$table-border-color: #ececec;
$background-color: #fafafa;
/*
@@ -20,7 +20,7 @@ $background-color: #fafafa;
*/
$gl-font-size: 15px;
$gl-title-color: #333;
-$gl-text-color: #555;
+$gl-text-color: #5c5c5c;
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
@@ -30,6 +30,7 @@ $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
+$gl-gray-dark: #313236;
$gl-header-color: $gl-title-color;
/*
@@ -65,7 +66,7 @@ $gl-padding-top: 10px;
$row-hover: #f4f8fe;
$progress-color: #c0392b;
$avatar_radius: 50%;
-$header-height: 58px;
+$header-height: 50px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
@@ -74,6 +75,9 @@ $btn-transparent-color: #8f8f8f;
$settings-icon-size: 18px;
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
+$link-underline-blue: #4a8bee;
+$layout-link-gray: #7e7c7c;
+$todo-alert-blue: #428bca;
/*
* Color schema
@@ -108,6 +112,7 @@ $red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
+$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 201f3e5ca46..aa41565f812 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -83,3 +83,12 @@
}
}
}
+
+table.builds {
+
+ .build-link {
+ a {
+ color: $gl-dark-link-color;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 358d2f4ab9d..c2cd227571f 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -31,9 +31,23 @@
}
.commit-committer-link,
.commit-author-link {
- color: #444;
+ color: $gl-gray;
font-weight: bold;
}
+
+ .time_ago {
+ margin-left: 8px;
+ }
+
+ .fa-clipboard {
+ color: $dropdown-title-btn-color;
+ }
+
+ .commit-info {
+ &.branches {
+ margin-left: 8px;
+ }
+ }
}
.commit-box {
@@ -42,7 +56,7 @@
.commit-title {
margin: 0;
font-size: 23px;
- color: #313236;
+ color: $gl-gray-dark;
}
.commit-description {
@@ -83,6 +97,14 @@
}
}
+.commit-action-buttons {
+ i {
+ color: $gl-icon-color;
+ font-size: 13px;
+ margin-right: 3px;
+ }
+}
+
/*
* Commit message textarea for web editor and
* custom merge request message
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 3438dbe4958..5e61e61d85c 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -22,7 +22,7 @@
.title {
margin: 0;
font-size: 23px;
- color: #313236;
+ color: $gl-gray-dark;
}
.description {
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index ee95bdf488e..4a95b7b852e 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -55,25 +55,6 @@
}
}
-.modal-body {
- position: relative;
- overflow-y: auto;
- padding: 15px;
-
- .form-actions {
- margin: -$gl-padding+1;
- margin-top: 15px;
- }
-}
-
-body.modal-open {
- overflow: hidden;
-}
-
-.modal .modal-dialog {
- width: 860px;
-}
-
.documentation {
padding: 7px;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 4ef548ffbe7..c4005ba1e69 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -104,7 +104,7 @@
font-weight: 600;
font-size: 17px;
margin: 5px 0;
- color: #313236;
+ color: $gl-gray-dark;
}
p:last-child {
@@ -136,7 +136,7 @@
}
.label-branch {
- color: #313236;
+ color: $gl-gray-dark;
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
@@ -272,3 +272,19 @@
display: inline-block;
width: 250px;
}
+
+.table-holder {
+ .builds {
+
+ th {
+ background-color: $white-light;
+ color: $gl-placeholder-color;
+ }
+
+ th,
+ td {
+ padding: 16px;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index d0e72a4422c..b94f524b513 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -28,7 +28,7 @@ li.milestone {
// Issue title
span a {
- color: rgba(0,0,0,0.64);
+ color: $gl-text-color;
}
}
}
@@ -51,7 +51,7 @@ li.milestone {
margin-top: 7px;
.issuable-number {
- color: rgba(0,0,0,0.44);
+ color: $gl-placeholder-color;
margin-right: 5px;
}
.avatar {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 9619d65db85..624c8249f7e 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -114,10 +114,6 @@ ul.notes {
word-break: keep-all;
}
}
-
- a {
- word-break: break-all;
- }
}
.note-header {
@@ -172,6 +168,11 @@ ul.notes {
.notes {
background-color: $white-light;
}
+
+ a code {
+ top: 0;
+ margin-right: 0;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 01f98479623..abc5a0e9877 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -205,3 +205,21 @@
text-align: center;
}
}
+
+.user-profile {
+ @media (max-width: $screen-xs-max) {
+ .cover-block {
+ padding-top: 20px;
+ }
+
+ .cover-controls {
+ position: static;
+ margin-bottom: 20px;
+
+ .btn {
+ display: inline-block;
+ width: 48%;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 99108e9bfc4..c20f04653fc 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -178,7 +178,7 @@
.option-title {
font-weight: normal;
display: inline-block;
- color: #313236;
+ color: $gl-gray-dark;
}
.option-descr {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index dbb6daf0d70..2370d35924e 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -1,7 +1,7 @@
.container-fluid {
.ci-status {
padding: 2px 7px;
- margin-right: 5px;
+ margin-right: 10px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 75f78569e3c..e51c3491dae 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,9 +6,16 @@
.navbar-nav {
li {
.badge.todos-pending-count {
- background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
+ background: $todo-alert-blue;
+ margin-left: -17px;
+ font-size: 11px;
+ color: white;
+ padding: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 3px;
}
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 25b5e95583e..a84fc2e0318 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -16,7 +16,7 @@
tr {
> td, > th {
- line-height: 26px;
+ line-height: 23px;
}
&:hover {
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 9083bfb41cf..cf795d977ce 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- return render_404 unless current_user.is_admin?
- end
-
- def authorize_impersonator!
- if session[:impersonator_id]
- User.find_by!(username: session[:impersonator_id]).admin?
- end
+ render_404 unless current_user.is_admin?
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 93c4894ea0f..4e85b6b4cf2 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
+ params.require(:hook).permit(
+ :enable_ssl_verification,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
deleted file mode 100644
index bf98af78615..00000000000
--- a/app/controllers/admin/impersonation_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-class Admin::ImpersonationController < Admin::ApplicationController
- skip_before_action :authenticate_admin!, only: :destroy
-
- before_action :user
- before_action :authorize_impersonator!
-
- def create
- if @user.blocked?
- flash[:alert] = "You cannot impersonate a blocked user"
-
- redirect_to admin_user_path(@user)
- else
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = admin_user_path(@user)
-
- warden.set_user(user, scope: 'user')
-
- flash[:alert] = "You are impersonating #{user.username}."
-
- redirect_to root_path
- end
- end
-
- def destroy
- redirect = session[:impersonator_return_to]
-
- warden.set_user(user, scope: 'user')
-
- session[:impersonator_return_to] = nil
- session[:impersonator_id] = nil
-
- redirect_to redirect || root_path
- end
-
- def user
- @user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
- end
-end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
new file mode 100644
index 00000000000..8be35f00a77
--- /dev/null
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -0,0 +1,26 @@
+class Admin::ImpersonationsController < Admin::ApplicationController
+ skip_before_action :authenticate_admin!
+ before_action :authenticate_impersonator!
+
+ def destroy
+ original_user = current_user
+
+ warden.set_user(impersonator, scope: :user)
+
+ Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}")
+
+ session[:impersonator_id] = nil
+
+ redirect_to admin_user_path(original_user)
+ end
+
+ private
+
+ def impersonator
+ @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+ end
+
+ def authenticate_impersonator!
+ render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9abf08d0e19..f2f654c7bcd 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -31,6 +31,24 @@ class Admin::UsersController < Admin::ApplicationController
user
end
+ def impersonate
+ if user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
+
+ redirect_to admin_user_path(user)
+ else
+ session[:impersonator_id] = current_user.id
+
+ warden.set_user(user, scope: :user)
+
+ Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}")
+
+ flash[:alert] = "You are now impersonating #{user.username}"
+
+ redirect_to root_path
+ end
+ end
+
def block
if user.block
redirect_back_or_admin_user(notice: "Successfully blocked")
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c53b0b21a3..17b3f49aed1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- current_application_settings.after_sign_out_path || new_user_session_path
+ current_application_settings.after_sign_out_path.presence || new_user_session_path
end
def abilities
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 1420b96840c..a52c614b259 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else
- @repository.commits(@ref, @path, @limit, @offset)
+ @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index d13ea9f34b6..092ef32e6e3 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
- @commits = @project.repository.commits(@ref, nil, 2000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
- @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = []
@commits.each do |commit|
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 5fd4f855dec..dfa9bd259e8 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :push_events, :issues_events,
- :merge_requests_events, :tag_push_events, :note_events,
- :build_events, :enable_ssl_verification)
+ params.require(:hook).permit(
+ :build_events,
+ :enable_ssl_verification,
+ :issues_events,
+ :merge_requests_events,
+ :note_events,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 9face235baa..016f5dd0005 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -96,6 +96,8 @@ class Projects::IssuesController < Projects::ApplicationController
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
+ return render_404 unless issue.can_move?(current_user, new_project)
+
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index c02bc28acef..0d6c32fabd2 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -40,10 +40,10 @@ class Projects::WikisController < Projects::ApplicationController
end
def update
- @page = @project_wiki.find_page(params[:id])
-
return render('empty') unless can?(current_user, :create_wiki, @project)
+ @page = @project_wiki.find_page(params[:id])
+
if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 059b88e2253..352bff19383 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+ # To avoid duplicate form fields on the login page, the registration form
+ # names fields using `new_user`, but Devise still wants the params in
+ # `user`.
+ if params["new_#{resource_name}"].present? && params[resource_name].blank?
+ params[resource_name] = params.delete(:"new_#{resource_name}")
+ end
+
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index a41172816b8..01cbf91c658 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh
if current_user
- if project.team.member?(current_user.id)
+ if project.team.member?(current_user.id) || current_user.admin?
snippets
else
snippets.public_and_internal
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 474c6f27374..93241b3afb7 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -131,7 +131,7 @@ module BlobHelper
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
def sanitize_svg(blob)
- blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
+ blob.data = Gitlab::Sanitizers::SVG.clean(blob.data)
blob
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index afe1e11a0da..198d39455d7 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,31 +16,49 @@ module IssuesHelper
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.project_path
- else
- project.issues_tracker.project_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.project_path
+ else
+ project.issues_tracker.project_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.new_issue_path
- else
- project.issues_tracker.new_issue_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.new_issue_path
+ else
+ project.issues_tracker.new_issue_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
- else
- project.issues_tracker.issue_url(issue_iid)
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def bulk_update_milestone_options
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 5d86bd490a8..3aa41030453 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -34,10 +34,13 @@ module NavHelper
end
def nav_header_class
- if nav_menu_collapsed?
- "header-collapsed"
- else
- "header-expanded"
- end
+ class_name =
+ if nav_menu_collapsed?
+ "header-collapsed"
+ else
+ "header-expanded"
+ end
+ class_name += " with-horizontal-nav" if defined?(nav) && nav
+ class_name
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 3d5e61d2c18..f17d02a7a3f 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -200,12 +200,8 @@ module ProjectsHelper
end
def repository_size(project = @project)
- "#{project.repository_size} MB"
- rescue
- # In order to prevent 500 error
- # when application cannot allocate memory
- # to calculate repo size - just show 'Unknown'
- 'unknown'
+ size_in_bytes = project.repository_size * 1.megabyte
+ number_to_human_size(size_in_bytes, delimiter: ',', precision: 2)
end
def default_url_to_repo(project = @project)
@@ -341,4 +337,10 @@ module ProjectsHelper
)
end
end
+
+ def sanitize_repo_path(message)
+ return '' unless message.present?
+
+ message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]")
+ end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index cdc40b81ee1..96116e916dd 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -28,6 +28,14 @@ module Emails
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
+ def note_snippet_email(recipient_id, note_id)
+ setup_note_mail(note_id, recipient_id)
+
+ @snippet = @note.noteable
+ @target_url = namespace_project_snippet_url(*note_target_url_options)
+ mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ end
+
private
def note_target_url_options
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 4cf8dd9a8ce..4528760fefa 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,3 +1,16 @@
+# == Schema Information
+#
+# Table name: appearances
+#
+# id :integer not null, primary key
+# title :string
+# description :text
+# header_logo :string
+# logo :string
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
class Appearance < ActiveRecord::Base
validates :title, presence: true
validates :description, presence: true
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 36f88154232..72ec91d2909 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -10,21 +10,20 @@
# sign_in_text :text
# created_at :datetime
# updated_at :datetime
-# home_page_url :string(255)
+# home_page_url :string
# default_branch_protection :integer default(2)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
-# default_group_visibility :integer
# restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
+# after_sign_out_path :string
# session_expire_delay :integer default(10080), not null
# import_sources :text
# help_page_text :text
-# admin_notification_email :string(255)
+# admin_notification_email :string
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
# runners_registration_token :string
@@ -32,8 +31,6 @@
# two_factor_grace_period :integer default(48)
# metrics_enabled :boolean default(FALSE)
# metrics_host :string default("localhost")
-# metrics_username :string
-# metrics_password :string
# metrics_pool_size :integer default(16)
# metrics_timeout :integer default(10)
# metrics_method_call_threshold :integer default(10)
@@ -41,9 +38,16 @@
# recaptcha_site_key :string
# recaptcha_private_key :string
# metrics_port :integer default(8089)
+# metrics_sample_interval :integer default(15)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
+# akismet_enabled :boolean default(FALSE)
+# akismet_api_key :string
# email_author_in_body :boolean default(FALSE)
+# default_group_visibility :integer
+# repository_checks_enabled :boolean default(FALSE)
+# metrics_packet_size :integer default(1)
+# shared_runners_text :text
#
class ApplicationSetting < ActiveRecord::Base
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 0ed0dd98a59..44b090260e7 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -4,9 +4,9 @@
#
# id :integer not null, primary key
# author_id :integer not null
-# type :string(255) not null
+# type :string not null
# entity_id :integer not null
-# entity_type :string(255) not null
+# entity_type :string not null
# details :text
# created_at :datetime
# updated_at :datetime
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 8a0a8a4c2a9..075ac733bfc 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -8,8 +8,8 @@
# ends_at :datetime
# created_at :datetime
# updated_at :datetime
-# color :string(255)
-# font :string(255)
+# color :string
+# font :string
#
class BroadcastMessage < ActiveRecord::Base
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index c2ddee527e5..2fea8047147 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# project_id :integer
-# status :string(255)
+# status :string
# finished_at :datetime
# trace :text
# created_at :datetime
@@ -15,19 +15,19 @@
# commit_id :integer
# commands :text
# job_id :integer
-# name :string(255)
+# name :string
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
+# stage :string
# trigger_request_id :integer
# stage_idx :integer
# tag :boolean
-# ref :string(255)
+# ref :string
# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
+# type :string
+# target_url :string
+# description :string
# artifacts_file :text
# gl_project_id :integer
# artifacts_metadata :text
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f2667e5476b..4ac4e0fb8b2 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -4,9 +4,9 @@
#
# id :integer not null, primary key
# project_id :integer
-# ref :string(255)
-# sha :string(255)
-# before_sha :string(255)
+# ref :string
+# sha :string
+# before_sha :string
# push_data :text
# created_at :datetime
# updated_at :datetime
@@ -14,6 +14,10 @@
# yaml_errors :text
# committed_at :datetime
# gl_project_id :integer
+# status :string
+# started_at :datetime
+# finished_at :datetime
+# duration :integer
#
module Ci
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 90349a07594..add59a08892 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -3,18 +3,18 @@
# Table name: ci_runners
#
# id :integer not null, primary key
-# token :string(255)
+# token :string
# created_at :datetime
# updated_at :datetime
-# description :string(255)
+# description :string
# contacted_at :datetime
# active :boolean default(TRUE), not null
# is_shared :boolean default(FALSE)
-# name :string(255)
-# version :string(255)
-# revision :string(255)
-# platform :string(255)
-# architecture :string(255)
+# name :string
+# version :string
+# revision :string
+# platform :string
+# architecture :string
#
module Ci
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 2b9a457c8ab..4f3f4d79fac 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -3,7 +3,7 @@
# Table name: ci_triggers
#
# id :integer not null, primary key
-# token :string(255)
+# token :string
# project_id :integer
# deleted_at :datetime
# created_at :datetime
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index e786bd7dd93..4229fe085a1 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -4,11 +4,11 @@
#
# id :integer not null, primary key
# project_id :integer
-# key :string(255)
+# key :string
# value :text
# encrypted_value :text
-# encrypted_value_salt :string(255)
-# encrypted_value_iv :string(255)
+# encrypted_value_salt :string
+# encrypted_value_iv :string
# gl_project_id :integer
#
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index aa56314aa16..1260c448de3 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# project_id :integer
-# status :string(255)
+# status :string
# finished_at :datetime
# trace :text
# created_at :datetime
@@ -15,21 +15,24 @@
# commit_id :integer
# commands :text
# job_id :integer
-# name :string(255)
+# name :string
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
+# stage :string
# trigger_request_id :integer
# stage_idx :integer
# tag :boolean
-# ref :string(255)
+# ref :string
# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
+# type :string
+# target_url :string
+# description :string
# artifacts_file :text
# gl_project_id :integer
+# artifacts_metadata :text
+# erased_by_id :integer
+# erased_at :datetime
#
class CommitStatus < ActiveRecord::Base
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d5166e81474..2e4efc4e8d8 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -35,13 +35,14 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
- scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
- scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :order_milestone_due_desc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date DESC, milestones.id DESC') }
+ scope :order_milestone_due_asc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date ASC, milestones.id ASC') }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
+ scope :outer_join_milestone, -> { joins("LEFT OUTER JOIN milestones ON milestones.id = #{table_name}.milestone_id") }
delegate :name,
:email,
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 9ab663c04ad..43cf625f770 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -7,9 +7,9 @@
# created_at :datetime
# updated_at :datetime
# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
+# title :string
+# type :string
+# fingerprint :string
# public :boolean default(FALSE), not null
#
diff --git a/app/models/email.rb b/app/models/email.rb
index b323d1edd10..eae2472f337 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# user_id :integer not null
-# email :string(255) not null
+# email :string not null
# created_at :datetime
# updated_at :datetime
#
diff --git a/app/models/event.rb b/app/models/event.rb
index 897518aadc7..25c7c3e6dc7 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -3,9 +3,9 @@
# Table name: events
#
# id :integer not null, primary key
-# target_type :string(255)
+# target_type :string
# target_id :integer
-# title :string(255)
+# title :string
# data :text
# project_id :integer
# created_at :datetime
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 97f4f03a9a5..d4afd8cbe84 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# project_id :integer
-# status :string(255)
+# status :string
# finished_at :datetime
# trace :text
# created_at :datetime
@@ -15,21 +15,24 @@
# commit_id :integer
# commands :text
# job_id :integer
-# name :string(255)
+# name :string
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
+# stage :string
# trigger_request_id :integer
# stage_idx :integer
# tag :boolean
-# ref :string(255)
+# ref :string
# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
+# type :string
+# target_url :string
+# description :string
# artifacts_file :text
# gl_project_id :integer
+# artifacts_metadata :text
+# erased_by_id :integer
+# erased_at :datetime
#
class GenericCommitStatus < CommitStatus
diff --git a/app/models/group.rb b/app/models/group.rb
index 1f8432e3320..cff76877958 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -2,16 +2,17 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# visibility_level :integer default(20), not null
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
+# id :integer not null, primary key
+# name :string not null
+# path :string not null
+# owner_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string
+# description :string default(""), not null
+# avatar :string
+# share_with_group_lock :boolean default(FALSE)
+# visibility_level :integer default(20), not null
#
require 'carrierwave/orm/activerecord'
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index bc6e0f98c3c..2b8f34a0568 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -16,6 +16,8 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# wiki_page_events :boolean default(FALSE), not null
+# token :string
#
class ProjectHook < WebHook
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 80962264ba2..0e176de5ef8 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -16,6 +16,8 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# wiki_page_events :boolean default(FALSE), not null
+# token :string
#
class ServiceHook < WebHook
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 15dddcc2447..ad508cbbcb8 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -16,6 +16,8 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# wiki_page_events :boolean default(FALSE), not null
+# token :string
#
class SystemHook < WebHook
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 3a2e4f546f7..8e58c9583ab 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -16,6 +16,8 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# wiki_page_events :boolean default(FALSE), not null
+# token :string
#
class WebHook < ActiveRecord::Base
@@ -43,23 +45,17 @@ class WebHook < ActiveRecord::Base
if parsed_url.userinfo.blank?
response = WebHook.post(url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification)
else
- post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ post_url = url.gsub("#{parsed_url.userinfo}@", '')
auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password),
}
response = WebHook.post(post_url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification,
basic_auth: auth)
end
@@ -73,4 +69,15 @@ class WebHook < ActiveRecord::Base
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
end
+
+ private
+
+ def build_headers(hook_name)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => hook_name.singularize.titleize
+ }
+ headers['X-Gitlab-Token'] = token if token.present?
+ headers
+ end
end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index e1915b079d4..ef4d5f99091 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -3,8 +3,8 @@
# Table name: identities
#
# id :integer not null, primary key
-# extern_uid :string(255)
-# provider :string(255)
+# extern_uid :string
+# provider :string
# user_id :integer
# created_at :datetime
# updated_at :datetime
diff --git a/app/models/issue.rb b/app/models/issue.rb
index ea1bfb776ee..abaa509707c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -3,20 +3,23 @@
# Table name: issues
#
# id :integer not null, primary key
-# title :string(255)
+# title :string
# assignee_id :integer
# author_id :integer
# project_id :integer
# created_at :datetime
# updated_at :datetime
# position :integer default(0)
-# branch_name :string(255)
+# branch_name :string
# description :text
# milestone_id :integer
-# state :string(255)
+# state :string
# iid :integer
# updated_by_id :integer
# moved_to_id :integer
+# confidential :boolean default(FALSE)
+# deleted_at :datetime
+# due_date :date
#
require 'carrierwave/orm/activerecord'
diff --git a/app/models/key.rb b/app/models/key.rb
index 0282ad18139..b2b57849f8a 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -7,9 +7,9 @@
# created_at :datetime
# updated_at :datetime
# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
+# title :string
+# type :string
+# fingerprint :string
# public :boolean default(FALSE), not null
#
diff --git a/app/models/label.rb b/app/models/label.rb
index 60bdce32952..9a22398d952 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -2,14 +2,14 @@
#
# Table name: labels
#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
-# description :string(255)
+# id :integer not null, primary key
+# title :string
+# color :string
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# template :boolean default(FALSE)
+# description :string
#
class Label < ActiveRecord::Base
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index b94c9c777af..7b8e872b6dd 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -5,7 +5,7 @@
# id :integer not null, primary key
# label_id :integer
# target_id :integer
-# target_type :string(255)
+# target_type :string
# created_at :datetime
# updated_at :datetime
#
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 86b1b7e2f99..927e764af92 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -3,11 +3,11 @@
# Table name: lfs_objects
#
# id :integer not null, primary key
-# oid :string(255) not null
+# oid :string not null
# size :integer not null
# created_at :datetime
# updated_at :datetime
-# file :string(255)
+# file :string
#
class LfsObject < ActiveRecord::Base
diff --git a/app/models/member.rb b/app/models/member.rb
index 60efafef211..cca82da89f1 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -5,15 +5,15 @@
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
-# source_type :string(255) not null
+# source_type :string not null
# user_id :integer
# notification_level :integer not null
-# type :string(255)
+# type :string
# created_at :datetime
# updated_at :datetime
# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
+# invite_email :string
+# invite_token :string
# invite_accepted_at :datetime
#
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 9fb474a1a93..a48c1943e6f 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -5,15 +5,15 @@
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
-# source_type :string(255) not null
+# source_type :string not null
# user_id :integer
# notification_level :integer not null
-# type :string(255)
+# type :string
# created_at :datetime
# updated_at :datetime
# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
+# invite_email :string
+# invite_token :string
# invite_accepted_at :datetime
#
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 07ddb02ae9d..143350a0b55 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -5,15 +5,15 @@
# id :integer not null, primary key
# access_level :integer not null
# source_id :integer not null
-# source_type :string(255) not null
+# source_type :string not null
# user_id :integer
# notification_level :integer not null
-# type :string(255)
+# type :string
# created_at :datetime
# updated_at :datetime
# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
+# invite_email :string
+# invite_token :string
# invite_accepted_at :datetime
#
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d00919c3b0c..4175e1e5fba 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -3,28 +3,29 @@
# Table name: merge_requests
#
# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
+# target_branch :string not null
+# source_branch :string not null
# source_project_id :integer not null
# author_id :integer
# assignee_id :integer
-# title :string(255)
+# title :string
# created_at :datetime
# updated_at :datetime
# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
+# state :string
+# merge_status :string
# target_project_id :integer not null
# iid :integer
# description :text
# position :integer default(0)
# locked_at :datetime
# updated_by_id :integer
-# merge_error :string(255)
+# merge_error :string
# merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer
# merge_commit_sha :string
+# deleted_at :datetime
#
class MergeRequest < ActiveRecord::Base
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 0580cafdd1b..8951e92a0b8 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -3,12 +3,14 @@
# Table name: merge_request_diffs
#
# id :integer not null, primary key
-# state :string(255)
+# state :string
# st_commits :text
# st_diffs :text
# merge_request_id :integer not null
# created_at :datetime
# updated_at :datetime
+# base_commit_sha :string
+# real_size :string
#
class MergeRequestDiff < ActiveRecord::Base
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 986184dd301..5ee8a965ad8 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -3,13 +3,13 @@
# Table name: milestones
#
# id :integer not null, primary key
-# title :string(255) not null
+# title :string not null
# project_id :integer not null
# description :text
# due_date :date
# created_at :datetime
# updated_at :datetime
-# state :string(255)
+# state :string
# iid :integer
#
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 55842df1e2d..741e912171d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -2,15 +2,17 @@
#
# Table name: namespaces
#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
+# id :integer not null, primary key
+# name :string not null
+# path :string not null
+# owner_id :integer
+# created_at :datetime
+# updated_at :datetime
+# type :string
+# description :string default(""), not null
+# avatar :string
+# share_with_group_lock :boolean default(FALSE)
+# visibility_level :integer default(20), not null
#
class Namespace < ActiveRecord::Base
diff --git a/app/models/note.rb b/app/models/note.rb
index 71b4293d57a..deee2b9e885 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -4,14 +4,14 @@
#
# id :integer not null, primary key
# note :text
-# noteable_type :string(255)
+# noteable_type :string
# author_id :integer
# created_at :datetime
# updated_at :datetime
# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
+# attachment :string
+# line_code :string
+# commit_id :string
# noteable_id :integer
# system :boolean default(FALSE), not null
# st_diff :text
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 5001738f411..846773752a6 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,3 +1,16 @@
+# == Schema Information
+#
+# Table name: notification_settings
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# source_id :integer not null
+# source_type :string not null
+# level :integer default(0), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
class NotificationSetting < ActiveRecord::Base
enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 }
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 452f3913eef..1d5f4c50254 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -3,14 +3,14 @@
# Table name: snippets
#
# id :integer not null, primary key
-# title :string(255)
+# title :string
# content :text
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
+# file_name :string
+# type :string
# visibility_level :integer default(0), not null
#
diff --git a/app/models/project.rb b/app/models/project.rb
index 76265a59ea7..bce25455373 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2,41 +2,46 @@
#
# Table name: projects
#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# description :text
-# created_at :datetime
-# updated_at :datetime
-# creator_id :integer
-# issues_enabled :boolean default(TRUE), not null
-# wall_enabled :boolean default(TRUE), not null
-# merge_requests_enabled :boolean default(TRUE), not null
-# wiki_enabled :boolean default(TRUE), not null
-# namespace_id :integer
-# issues_tracker :string(255) default("gitlab"), not null
-# issues_tracker_id :string(255)
-# snippets_enabled :boolean default(TRUE), not null
-# last_activity_at :datetime
-# import_url :string(255)
-# visibility_level :integer default(0), not null
-# archived :boolean default(FALSE), not null
-# avatar :string(255)
-# import_status :string(255)
-# repository_size :float default(0.0)
-# star_count :integer default(0), not null
-# import_type :string(255)
-# import_source :string(255)
-# commit_count :integer default(0)
-# import_error :text
-# ci_id :integer
-# builds_enabled :boolean default(TRUE), not null
-# shared_runners_enabled :boolean default(TRUE), not null
-# runners_token :string
-# build_coverage_regex :string
-# build_allow_git_fetch :boolean default(TRUE), not null
-# build_timeout :integer default(3600), not null
-# pending_delete :boolean
+# id :integer not null, primary key
+# name :string
+# path :string
+# description :text
+# created_at :datetime
+# updated_at :datetime
+# creator_id :integer
+# issues_enabled :boolean default(TRUE), not null
+# wall_enabled :boolean default(TRUE), not null
+# merge_requests_enabled :boolean default(TRUE), not null
+# wiki_enabled :boolean default(TRUE), not null
+# namespace_id :integer
+# issues_tracker :string default("gitlab"), not null
+# issues_tracker_id :string
+# snippets_enabled :boolean default(TRUE), not null
+# last_activity_at :datetime
+# import_url :string
+# visibility_level :integer default(0), not null
+# archived :boolean default(FALSE), not null
+# avatar :string
+# import_status :string
+# repository_size :float default(0.0)
+# star_count :integer default(0), not null
+# import_type :string
+# import_source :string
+# commit_count :integer default(0)
+# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
+# pending_delete :boolean default(FALSE)
+# public_builds :boolean default(TRUE), not null
+# main_language :string
+# pushes_since_gc :integer default(0)
+# last_repository_check_failed :boolean
+# last_repository_check_at :datetime
#
require 'carrierwave/orm/activerecord'
@@ -740,19 +745,17 @@ class Project < ActiveRecord::Base
end
def open_branches
- all_branches = repository.branches
+ # We're using a Set here as checking values in a large Set is faster than
+ # checking values in a large Array.
+ protected_set = Set.new(protected_branch_names)
- if protected_branches.present?
- all_branches.reject! do |branch|
- protected_branches_names.include?(branch.name)
- end
+ repository.branches.reject do |branch|
+ protected_set.include?(branch.name)
end
-
- all_branches
end
- def protected_branches_names
- @protected_branches_names ||= protected_branches.map(&:name)
+ def protected_branch_names
+ @protected_branch_names ||= protected_branches.pluck(:name)
end
def root_ref?(branch)
@@ -769,7 +772,7 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
- protected_branches_names.include?(branch_name)
+ protected_branches.where(name: branch_name).any?
end
def developers_can_push_to_protected_branch?(branch_name)
@@ -1041,4 +1044,11 @@ class Project < ActiveRecord::Base
def wiki
@wiki ||= ProjectWiki.new(self, self.owner)
end
+
+ def schedule_delete!(user_id, params)
+ # Queue this task for after the commit, so once we mark pending_delete it will run
+ run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }
+
+ update_attribute(:pending_delete, true)
+ end
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index e52a6bd7c84..66f5a609bf5 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: project_group_links
+#
+# id :integer not null, primary key
+# project_id :integer not null
+# group_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# group_access :integer default(30), not null
+#
+
class ProjectGroupLink < ActiveRecord::Base
GUEST = 10
REPORTER = 20
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 2c0ae312f1b..7830f764ed3 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -2,9 +2,12 @@
#
# Table name: project_import_data
#
-# id :integer not null, primary key
-# project_id :integer
-# data :text
+# id :integer not null, primary key
+# project_id :integer
+# data :text
+# encrypted_credentials :text
+# encrypted_credentials_iv :string
+# encrypted_credentials_salt :string
#
require 'carrierwave/orm/activerecord'
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 792ad804575..368485a060a 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
require 'asana'
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 29d841faed8..ffb7455b014 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class AssemblaService < Service
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 060062aaf7a..c36ee95e378 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class BambooService < CiService
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 3efbfd2eec3..f9f4897a065 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
require "addressable/uri"
@@ -26,7 +29,7 @@ class BuildkiteService < CiService
prop_accessor :project_url, :token, :enable_ssl_verification
- validates :project_url, presence: true, if: :activated?
+ validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -91,7 +94,7 @@ class BuildkiteService < CiService
{ type: 'text',
name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" },
-
+
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 6ab6d7417b7..20cdfcaffb2 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class BuildsEmailService < Service
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 6e8f0842524..28c969fe57f 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class CampfireService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index d9f0849d147..9bc8f982da6 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
# Base class for CI services
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 88a3e9218cb..4d1319eb6f8 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class CustomIssueTrackerService < IssueTrackerService
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index b4724bb647e..d8e00e018cc 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class DroneCiService < CiService
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b831577cd97..2dbd29062df 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class EmailsOnPushService < Service
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index b402b68665a..5469049bb5e 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class ExternalWikiService < Service
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 8605ce66e48..3dc1e0fbe8b 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
require "flowdock-git-hook"
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 61babe9cfe5..b4c311cf664 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
require "gemnasium/gitlab_service"
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 33f0d7ea01a..a92f7226083 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index eaa5654b9c6..1adaeeb3b2b 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class GitlabIssueTrackerService < IssueTrackerService
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 064ef8e8674..f9ddf588722 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class HipchatService < Service
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 04c714bfaad..b9a592d7096 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
require 'uri'
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 25045224ce5..98a3a7c6b86 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,11 +17,14 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class IssueTrackerService < Service
- validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+ validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker'
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1ed42c4f3e7..ba68658f0bd 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class JiraService < IssueTrackerService
@@ -28,6 +31,8 @@ class JiraService < IssueTrackerService
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
+ validates :api_url, presence: true, url: true, if: :activated?
+
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index c9a890c7e3f..acaa0c39365 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class PivotaltrackerService < Service
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index e76d9eca2ab..a640c8cb440 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class PushoverService < Service
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index de974354c77..e2137e92c62 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class RedmineService < IssueTrackerService
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index fd65027f084..83ffa53a407 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,12 +17,15 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class SlackService < Service
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
- validates :webhook, presence: true, if: :activated?
+ validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
if properties.nil?
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 8dceee5e2c5..4015da31509 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
class TeamcityService < CiService
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 1f7d85a5f3d..b4b2807eba4 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -3,14 +3,14 @@
# Table name: snippets
#
# id :integer not null, primary key
-# title :string(255)
+# title :string
# content :text
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
+# file_name :string
+# type :string
# visibility_level :integer default(0), not null
#
@@ -22,4 +22,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
+
+ participant :author, :notes
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 8ebd790a89e..3d2052c892c 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -4,7 +4,7 @@
#
# id :integer not null, primary key
# project_id :integer not null
-# name :string(255) not null
+# name :string not null
# created_at :datetime
# updated_at :datetime
# developers_can_push :boolean default(FALSE), not null
diff --git a/app/models/release.rb b/app/models/release.rb
index 89f70278af5..dc700d1ea5a 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -3,7 +3,7 @@
# Table name: releases
#
# id :integer not null, primary key
-# tag :string(255)
+# tag :string
# description :text
# project_id :integer
# created_at :datetime
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b4319297e43..7aebfe279fb 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -87,13 +87,15 @@ class Repository
nil
end
- def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
+ def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
options = {
repo: raw_repository,
ref: ref,
path: path,
limit: limit,
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,
@@ -146,10 +148,20 @@ class Repository
find_branch(branch_name)
end
- def add_tag(tag_name, ref, message = nil)
- before_push_tag
+ def add_tag(user, tag_name, target, message = nil)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ options = { message: message, tagger: user_to_committer(user) } if message
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.tags.create(tag_name, target, options)
+ end
- gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
+ find_tag(tag_name)
end
def rm_branch(user, branch_name)
@@ -575,7 +587,7 @@ class Repository
end
def contributors
- commits = self.commits(nil, nil, 2000, 0, true)
+ commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
diff --git a/app/models/security_event.rb b/app/models/security_event.rb
index 68c00adad59..0bee03974f1 100644
--- a/app/models/security_event.rb
+++ b/app/models/security_event.rb
@@ -4,9 +4,9 @@
#
# id :integer not null, primary key
# author_id :integer not null
-# type :string(255) not null
+# type :string not null
# entity_id :integer not null
-# entity_type :string(255) not null
+# entity_type :string not null
# details :text
# created_at :datetime
# updated_at :datetime
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 77115597d71..99279a2e083 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -5,11 +5,11 @@
# id :integer not null, primary key
# project_id :integer
# noteable_id :integer
-# noteable_type :string(255)
+# noteable_type :string
# recipient_id :integer
-# commit_id :string(255)
-# line_code :string(255)
-# reply_key :string(255) not null
+# commit_id :string
+# reply_key :string not null
+# line_code :string
#
class SentNotification < ActiveRecord::Base
diff --git a/app/models/service.rb b/app/models/service.rb
index 2645b8321d7..bf16a545307 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -3,12 +3,12 @@
# Table name: services
#
# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
+# type :string
+# title :string
# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
@@ -17,6 +17,9 @@
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
+# category :string default("common"), not null
+# default :boolean default(FALSE)
+# wiki_page_events :boolean default(TRUE)
#
# To add new service you should build a class inherited from Service
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 0fd08061925..2f905a90942 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -3,14 +3,14 @@
# Table name: snippets
#
# id :integer not null, primary key
-# title :string(255)
+# title :string
# content :text
# author_id :integer not null
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
+# file_name :string
+# type :string
# visibility_level :integer default(0), not null
#
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index 12df68ef83b..f49eb7d88e2 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -1,3 +1,20 @@
+# == Schema Information
+#
+# Table name: spam_logs
+#
+# id :integer not null, primary key
+# user_id :integer
+# source_ip :string
+# user_agent :string
+# via_api :boolean
+# project_id :integer
+# noteable_type :string
+# title :string
+# description :text
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
class SpamLog < ActiveRecord::Base
belongs_to :user
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index dd800ce110f..242faa7d32e 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -5,7 +5,7 @@
# id :integer not null, primary key
# user_id :integer
# subscribable_id :integer
-# subscribable_type :string(255)
+# subscribable_type :string
# subscribed :boolean
# created_at :datetime
# updated_at :datetime
diff --git a/app/models/user.rb b/app/models/user.rb
index b6f405c6981..959b1f93758 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -3,55 +3,55 @@
# Table name: users
#
# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
+# email :string default(""), not null
+# encrypted_password :string default(""), not null
+# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0)
# current_sign_in_at :datetime
# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
+# current_sign_in_ip :string
+# last_sign_in_ip :string
# created_at :datetime
# updated_at :datetime
-# name :string(255)
+# name :string
# admin :boolean default(FALSE), not null
# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
+# skype :string default(""), not null
+# linkedin :string default(""), not null
+# twitter :string default(""), not null
+# authentication_token :string
# theme_id :integer default(1), not null
-# bio :string(255)
+# bio :string
# failed_attempts :integer default(0)
# locked_at :datetime
-# username :string(255)
+# username :string
# can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null
-# state :string(255)
+# state :string
# color_scheme_id :integer default(1), not null
# notification_level :integer default(1), not null
# password_expires_at :datetime
# created_by_id :integer
# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
+# avatar :string
+# confirmation_token :string
# confirmed_at :datetime
# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
+# unconfirmed_email :string
# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
+# website_url :string default(""), not null
+# notification_email :string
# hide_no_password :boolean default(FALSE)
# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
+# location :string
+# encrypted_otp_secret :string
+# encrypted_otp_secret_iv :string
+# encrypted_otp_secret_salt :string
# otp_required_for_login :boolean default(FALSE), not null
# otp_backup_codes :text
-# public_email :string(255) default(""), not null
+# public_email :string default(""), not null
# dashboard :integer default(0)
# project_view :integer default(0)
# consumed_timestep :integer
@@ -59,7 +59,8 @@
# hide_project_limit :boolean default(FALSE)
# unlock_token :string
# otp_grace_period_started_at :datetime
-# external :boolean default(FALSE)
+# ldap_email :boolean default(FALSE), not null
+# external :boolean default(FALSE)
#
require 'carrierwave/orm/activerecord'
@@ -91,7 +92,7 @@ class User < ActiveRecord::Base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
- devise :lockable, :recoverable, :rememberable, :trackable,
+ devise :lockable, :async, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
attr_accessor :force_random_password
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 707c2f7ff85..9f4481a8153 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -43,9 +43,4 @@ class CreateBranchService < BaseService
out[:branch] = branch
out
end
-
- def build_push_data(project, user, branch)
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
- end
end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 55985380d31..91ed0e354d0 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -1,50 +1,30 @@
require_relative 'base_service'
class CreateTagService < BaseService
- def execute(tag_name, ref, message, release_description = nil)
+ def execute(tag_name, target, message, release_description = nil)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
- if valid_tag == false
- return error('Tag name invalid')
- end
+ return error('Tag name invalid') unless valid_tag
repository = project.repository
- existing_tag = repository.find_tag(tag_name)
- if existing_tag
- return error('Tag already exists')
- end
-
message.strip! if message
- repository.add_tag(tag_name, ref, message)
- new_tag = repository.find_tag(tag_name)
+ new_tag = nil
+ begin
+ new_tag = repository.add_tag(current_user, tag_name, target, message)
+ rescue Rugged::TagError
+ return error("Tag #{tag_name} already exists")
+ rescue GitHooksService::PreReceiveError
+ return error('Tag creation was rejected by Git hook')
+ end
if new_tag
- push_data = create_push_data(project, current_user, new_tag)
- EventCreateService.new.push(project, current_user, push_data)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, current_user, push_data)
-
if release_description
CreateReleaseService.new(@project, @current_user).
execute(tag_name, release_description)
end
-
- success(new_tag)
+ success.merge(tag: new_tag)
else
- error('Invalid reference name')
+ error("Target #{target} is invalid")
end
end
-
- def success(branch)
- out = super()
- out[:tag] = branch
- out
- end
-
- def create_push_data(project, user, tag)
- commits = [project.commit(tag.target)].compact
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message)
- end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index b7af80055bf..66136b62617 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -17,6 +17,7 @@ class GitPushService < BaseService
# 6. Checks if the project's main language has changed
#
def execute
+ @project.repository.after_create if @project.empty_repo?
@project.repository.after_push_commit(branch_name, params[:newrev])
if push_remove_branch?
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 64271d8bc5c..7410442609d 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,6 +2,7 @@ class GitTagPushService < BaseService
attr_accessor :push_data
def execute
+ project.repository.after_create if project.empty_repo?
project.repository.before_push_tag
@push_data = build_push_data
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 82e7090f1ea..e61628086f0 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -41,14 +41,25 @@ module Issues
private
def create_new_issue
- new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
+ new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
+ milestone_id: cloneable_milestone_id,
project: @new_project, author: @old_issue.author,
description: rewrite_content(@old_issue.description) }
- new_params = @old_issue.serializable_hash.merge(new_params)
+ new_params = @old_issue.serializable_hash.symbolize_keys.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
end
+ def cloneable_label_ids
+ @new_project.labels
+ .where(title: @old_issue.labels.pluck(:title)).pluck(:id)
+ end
+
+ def cloneable_milestone_id
+ @new_project.milestones
+ .find_by(title: @old_issue.milestone.try(:title)).try(:id)
+ end
+
def rewrite_notes
@old_issue.notes.find_each do |note|
new_note = note.dup
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd..cd4230aa5e4 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -7,6 +7,9 @@ module MergeRequests
merge_request.can_be_created = false
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
+
+ merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
@@ -38,21 +41,45 @@ module MergeRequests
merge_request.can_be_created = false
end
+ set_title_and_description(merge_request)
+ end
+
+ private
+
+ # When your branch name starts with an iid followed by a dash this pattern will be
+ # interpreted as the user wants to close that issue on this project.
+ #
+ # For example:
+ # - Issue 112 exists, title: Emoji don't show up in commit title
+ # - Source branch is: 112-fix-mep-mep
+ #
+ # Will lead to:
+ # - Appending `Closes #112` to the description
+ # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
+ # more than one commit in the MR
+ #
+ def set_title_and_description(merge_request)
+ if match = merge_request.source_branch.match(/\A(\d+)-/)
+ iid = match[1]
+ end
+
commits = merge_request.compare_commits
if commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
merge_request.description ||= commit.description.try(:strip)
+ elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?)
+ case issue
+ when Issue
+ merge_request.title = "Resolve \"#{issue.title}\""
+ when ExternalIssue
+ merge_request.title = "Resolve #{issue.title}"
+ end
else
merge_request.title = merge_request.source_branch.titleize.humanize
end
- # When your branch name starts with an iid followed by a dash this pattern will
- # be interpreted as the use wants to close that issue on this project
- # Pattern example: 112-fix-mep-mep
- # Will lead to appending `Closes #112` to the description
- if match = merge_request.source_branch.match(/\A(\d+)-/)
- iid = match[1]
+ if iid
closes_issue = "Closes ##{iid}"
if merge_request.description.present?
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2bb312bb252..01586994813 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,6 +5,8 @@ module Notes
note.author = current_user
note.system = false
+ return unless valid_project?(note)
+
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
@@ -13,5 +15,14 @@ module Notes
note
end
+
+ private
+
+ def valid_project?(note)
+ return false unless project
+ return true if note.for_commit?
+
+ note.noteable.try(:project) == project
+ end
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index df5054f08d7..19aab999e00 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -7,9 +7,7 @@ module Projects
DELETED_FLAG = '+deleted'
def pending_delete!
- project.update_attribute(:pending_delete, true)
-
- ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
+ project.schedule_delete!(current_user.id, params)
end
def execute
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
index 988c663b9d0..24a817c06c9 100644
--- a/app/services/wiki_pages/create_service.rb
+++ b/app/services/wiki_pages/create_service.rb
@@ -1,7 +1,8 @@
module WikiPages
class CreateService < WikiPages::BaseService
def execute
- page = WikiPage.new(@project.wiki)
+ project_wiki = ProjectWiki.new(@project, current_user)
+ page = WikiPage.new(project_wiki)
if page.create(@params)
execute_hooks(page, 'create')
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index 3571eefd570..967151bc33b 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -35,15 +35,15 @@
%td
#{build.stage} / #{build.name}
- .pull-right
- - if build.tags.any?
- - build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - if build.try(:trigger_request)
- %span.label.label-info triggered
- - if build.try(:allow_failure)
- %span.label.label-danger allowed to fail
+ %td
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(:allow_failure)
+ %span.label.label-danger allowed to fail
%td.duration
- if build.duration
@@ -61,12 +61,12 @@
%td
.pull-right
- if can?(current_user, :read_build, project) && build.artifacts?
- = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
+ = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
%i.fa.fa-download
- if can?(current_user, :update_build, build.project)
- if build.active?
- = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
+ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
- %i.fa.fa-repeat
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ %i.fa.fa-refresh
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 5931efdefe6..804d7851bdb 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -19,7 +19,7 @@
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-.gray-content-block.second-block
+.row-content-block.second-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
@@ -38,6 +38,7 @@
%th Ref
%th Runner
%th Name
+ %th Tags
%th Duration
%th Finished at
%th
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 67d23c80233..7b388cf7862 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -13,9 +13,15 @@
= form_errors(@hook)
.form-group
- = f.label :url, "URL:", class: 'control-label'
+ = f.label :url, 'URL', class: 'control-label'
.col-sm-10
- = f.text_field :url, class: "form-control"
+ = f.text_field :url, class: 'form-control'
+ .form-group
+ = f.label :token, 'Secret Token', class: 'control-label'
+ .col-sm-10
+ = f.text_field :token, class: 'form-control'
+ %p.help-block
+ Use this token to validate received payloads
.form-group
= f.label :url, "Trigger", class: 'control-label'
.col-sm-10.prepend-top-10
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 4b475a4d8fa..698feb571ac 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -7,7 +7,7 @@
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
= link_to klass::file_name, "##{klass::file_name_noext}",
'data-toggle' => 'tab'
-.gray-content-block
+.row-content-block
To prevent performance issues admin logs output the last 2000 lines
.tab-content
- loggers.each do |klass|
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 0ee8dc962b9..d6743081c8e 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
Without projects
%small.badge= number_with_delimiter(User.without_projects.count)
- .gray-content-block.second-block
+ .row-content-block.second-block
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 49ab8aad1d5..fc42e5dcc66 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -8,14 +8,14 @@
= link_to todos_filter_path(state: 'pending') do
%span
To do
- %span{class: 'badge'}
+ %span.badge
= todos_pending_count
- todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
- %span{class: 'badge'}
+ %span.badge
= todos_done_count
.nav-controls
@@ -25,7 +25,7 @@
= icon('spinner spin')
.todos-filters
- .gray-content-block.second-block
+ .row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
= select_tag('project_id', todo_projects_options,
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 22b2c1a186b..c9d1e454a5e 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -4,7 +4,7 @@
%h3 Two-factor Authentication
.login-body
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true
- %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes.
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true
+ %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
= f.submit "Verify code", class: "btn btn-save"
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index e5607dacd0d..510215bb8cd 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,7 +6,7 @@
.login-heading
%h3 Create an account
.login-body
- = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
@@ -16,7 +16,7 @@
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 5d622582088..e4629bae0e6 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -5,7 +5,7 @@
= cache [event, current_application_settings, "v2.2"] do
- if event.author
- = link_to user_path(event.author.username) do
+ = link_to user_path(event.author) do
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- else
= image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 5753158c24d..a1a282178e7 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -1,5 +1,5 @@
- if show_last_push_widget?(event)
- .gray-content-block.clear-block.last-push-widget
+ .row-content-block.clear-block.last-push-widget
.event-last-push
.event-last-push-text
%span You pushed to
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 8ffca96bb4e..57f6e7e0612 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -6,7 +6,7 @@
- else
= render 'explore/head'
-.gray-content-block.clearfix
+.row-content-block.clearfix
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 0f100c39ffb..9b838b9f3b7 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -6,7 +6,7 @@
- else
= render 'explore/head'
-.gray-content-block
+.row-content-block
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
index f73e1d9e865..aaad265b3ee 100644
--- a/app/views/groups/activity.html.haml
+++ b/app/views/groups/activity.html.haml
@@ -3,7 +3,6 @@
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
- page_title "Activity"
-- header_title group_title(@group, "Activity", activity_group_path(@group))
%section.activities
= render 'activities'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index a698cbbe9db..92cd4c553d0 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,3 @@
-- header_title group_title(@group, "Settings", edit_group_path(@group))
-
.panel.panel-default.prepend-top-default
.panel-heading
Group settings
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 6b7fd5746d6..0eb6bbd4420 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Members"
-- header_title group_title(@group, "Members", group_group_members_path(@group))
.group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group)
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index aea35c50862..4434f1cbd35 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,5 +1,4 @@
- page_title "Issues"
-- header_title group_title(@group, "Issues", issues_group_path(@group))
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
@@ -16,7 +15,7 @@
= render 'shared/issuable/filter', type: :issues
-.gray-content-block.second-block
+.row-content-block.second-block
Only issues from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index e1c9dd931ee..e6953d94531 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,5 +1,4 @@
- page_title "Merge Requests"
-- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.top-area
= render 'shared/issuable/nav', type: :merge_requests
@@ -8,7 +7,7 @@
= render 'shared/issuable/filter', type: :merge_requests
-.gray-content-block.second-block
+.row-content-block.second-block
Only merge requests from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index ab307708b75..121a7de3ad7 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Milestones"
-- header_title group_title(@group, "Milestones", group_milestones_path(@group))
.top-area
= render 'shared/milestones_filter'
@@ -10,7 +9,7 @@
= icon('plus')
New Milestone
-.gray-content-block
+.row-content-block
Only milestones from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index dd75766121e..c2f2d9912f7 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,5 +1,4 @@
- page_title "Projects"
-- header_title group_title(@group, "Projects", projects_group_path(@group))
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 3d16ecb097a..77c297255b8 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -4,28 +4,20 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-.cover-block
- .cover-controls
- - if @group && can?(current_user, :admin_group, @group)
- = link_to icon('pencil'), edit_group_path(@group), class: 'btn'
- - if current_user
- = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn'
-
- .avatar-holder
+.cover-block.groups-cover-block
+ .container-fluid.container-limited
= link_to group_icon(@group), target: '_blank' do
- = image_tag group_icon(@group), class: "avatar group-avatar s90"
- .cover-title
- %h1
- = @group.name
- %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
- = visibility_level_icon(@group.visibility_level, fw: false)
-
- .cover-desc.username
- @#{@group.path}
-
- - if @group.description.present?
- .cover-desc.description
- = markdown(@group.description, pipeline: :description)
+ = image_tag group_icon(@group), class: "avatar group-avatar s70"
+ .group-info
+ .cover-title
+ %h1
+ @#{@group.path}
+ %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, fw: false)
+
+ - if @group.description.present?
+ .cover-desc.description
+ = markdown(@group.description, pipeline: :description)
%div{ class: container_class }
.top-area
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index f12df5c8ffe..d676bc28c89 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -48,14 +48,14 @@
.lead
Gray content block with side padding using
- %code .gray-content-block
+ %code .row-content-block
.example
- .gray-content-block
+ .row-content-block
%h4 Normal block inside content
= lorem
- .gray-content-block.second-block
+ .row-content-block.second-block
%h4 Second block
= lorem
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index ad8a2e1e6c7..3c3bc41bf0e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -26,7 +26,7 @@
.layout-nav
.container-fluid
= render "layouts/nav/#{nav}"
- .content-wrapper
+ .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) }
= render "layouts/flash"
= yield :flash_message
%div{ class: (container_class unless @no_container) }
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 2e483b7148d..f06acc98ca1 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,6 +1,6 @@
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
-- sidebar "group" unless sidebar
+- nav "group"
= render template: "layouts/application"
diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml
index a1a1fc2f858..66b115e36de 100644
--- a/app/views/layouts/group_settings.html.haml
+++ b/app/views/layouts/group_settings.html.haml
@@ -1,5 +1,4 @@
- page_title "Settings"
-- header_title group_title(@group, "Settings", edit_group_path(@group))
-- sidebar "group_settings"
+- nav "group"
= render template: "layouts/group"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3beb8ff7c0d..86930d4eaaf 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -15,7 +15,7 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
@@ -23,6 +23,7 @@
= icon('wrench fw')
%li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('bell fw')
%span.badge.todos-pending-count
= todos_pending_count
- if current_user.can_create_project?
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index ca49c313ff7..fad4224e945 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -15,12 +15,12 @@
= icon('dashboard fw')
%span
Activity
- = nav_link(controller: :groups) do
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
- = nav_link(controller: :milestones) do
+ = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index f08c5edf99c..3b40006a0cc 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -4,7 +4,7 @@
= icon('bookmark fw')
%span
Projects
- = nav_link(controller: :groups) do
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 55940741dc0..3438005863a 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,12 +1,6 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
+= render 'layouts/nav/group_settings'
+%ul.nav-links
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= icon('group fw')
@@ -28,22 +22,16 @@
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.count= number_with_delimiter(issues.count)
+ %span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.count= number_with_delimiter(merge_requests.count)
+ %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
%span
Members
- - if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
- = link_to edit_group_path(@group), title: 'Settings' do
- = icon ('cogs fw')
- %span
- Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index 56a92fe9103..e391ec7f2b7 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,20 +1,20 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to group_path(@group), title: 'Go to group', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to group
-
- %li.separate-item
-
- %ul.sidebar-subnav
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'Group Settings' do
- = icon ('pencil-square-o fw')
- %span
- Group Settings
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
- = icon('folder fw')
- %span
- Projects
+- if current_user
+ - if access = @group.users.find_by(id: current_user.id)
+ .controls
+ %span.dropdown.group-settings-dropdown
+ %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - if can?(current_user, :admin_group, @group)
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects' do
+ Projects
+ %li.divider
+ %li
+ = link_to edit_group_path(@group) do
+ Edit Group
+ %li
+ = link_to leave_group_group_members_path(@group),
+ data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do
+ Leave Group
diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml
new file mode 100644
index 00000000000..2fa2f784661
--- /dev/null
+++ b/app/views/notify/note_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb
new file mode 100644
index 00000000000..4d5a406f4b0
--- /dev/null
+++ b/app/views/notify/note_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 57527361eb6..6f7fefdb46d 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -45,4 +45,4 @@
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index f59d27f7ed0..eef50d887c7 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -8,11 +8,11 @@
%p
- if @user.avatar?
You can change your avatar here
- - if Gitlab.config.gravatar.enabled
+ - if gravatar_enabled?
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
- else
You can upload an avatar here
- - if Gitlab.config.gravatar.enabled
+ - if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
.col-lg-9
.clearfix.avatar-image.append-bottom-default
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f0a3e416db7..7c2b8d01508 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,7 +1,7 @@
- if event = last_push_event
- if show_last_push_widget?(event)
- .gray-content-block.top-block.clear-block.hidden-xs
+ .row-content-block.top-block.clear-block.hidden-xs
.event-last-push
.event-last-push-text
%span You pushed to
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index a9908eaecca..369a847e7d4 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -7,7 +7,7 @@
= cache(readme_cache_key) do
= render_readme(readme)
- else
- .gray-content-block.second-block.center
+ .row-content-block.second-block.center
%h3.page-title
This project does not have a README yet
- if can?(current_user, :push_code, @project)
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 84034c8bf16..49f95ff37db 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,7 +1,7 @@
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
= render 'projects/builds/header_title'
-.top-block.gray-content-block.clearfix
+.top-block.row-content-block.clearfix
.pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default download' do
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 88266e21230..ac7790421a4 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Branches"
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.pull-right
- if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 0406fc21d77..2e8015d119b 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -9,6 +9,7 @@
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
+
%li{class: ('active' if @scope == 'running')}
= link_to project_builds_path(@project, scope: :running) do
Running
@@ -34,7 +35,7 @@
= icon('wrench')
%span CI Lint
-.gray-content-block
+.row-content-block
#{(@scope || 'running').capitalize} builds from this project
%ul.content-list
@@ -52,6 +53,7 @@
%th Ref
%th Stage
%th Name
+ %th Tags
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 99d72aa7935..c0f7a7686f0 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
.build-page
- .gray-content-block.top-block
+ .row-content-block.top-block
Build ##{@build.id} for commit
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
@@ -34,7 +34,7 @@
%i.fa.fa-warning
This build was retried.
- .gray-content-block.middle-block
+ .row-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index e123eb1cc7a..8e95f040273 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -40,6 +40,7 @@
%td
= build.name
+ %td
.label-container
- if build.tags.any?
- build.tags.each do |tag|
@@ -68,12 +69,12 @@
%td
.pull-right
- if can?(current_user, :read_build, build) && build.artifacts?
- = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
+ = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
%i.fa.fa-download
- if can?(current_user, :update_build, build)
- if build.active?
- = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
+ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
- %i.fa.fa-repeat
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ %i.fa.fa-refresh
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
index d3acd33116c..e849aefb188 100644
--- a/app/views/projects/commit/_ci_commit.html.haml
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.middle-block
+.row-content-block.build-content.middle-block
.pull-right
- if can?(current_user, :update_build, @project)
- if ci_commit.builds.latest.failed.any?(&:retryable?)
@@ -40,6 +40,7 @@
%th Build ID
%th Stage
%th Name
+ %th Tags
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
@@ -49,7 +50,7 @@
= render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
- if ci_commit.retried.any?
- .gray-content-block.second-block
+ .row-content-block.second-block
Retried builds
.table-holder
@@ -61,6 +62,7 @@
%th Ref
%th Stage
%th Name
+ %th Tags
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 3d7c18a5f58..01163e526b2 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,4 +1,4 @@
-.pull-right
+.pull-right.commit-action-buttons
%div
- if @notes_count > 0
%span.btn.disabled.btn-grouped
@@ -22,10 +22,12 @@
%div
%p
- %span.light Commit
- = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
- = clipboard_button(clipboard_text: @commit.id)
.commit-info-row
+ - if @commit.status
+ = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
+ = ci_icon_for_status(@commit.status)
+ build:
+ = ci_label_for_status(@commit.status)
%span.light Authored by
%strong
= commit_author_link(@commit, avatar: true, size: 24)
@@ -39,19 +41,15 @@
#{time_ago_with_tooltip(@commit.committed_date)}
.commit-info-row
+ %span.light Commit
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = clipboard_button(clipboard_text: @commit.id)
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
-- if @commit.status
- .pull-right
- = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
- = ci_icon_for_status(@commit.status)
- build:
- = ci_label_for_status(@commit.status)
-
-.commit-info-row.branches
- %i.fa.fa-spinner.fa-spin
+ %span.commit-info.branches
+ %i.fa.fa-spinner.fa-spin
.commit-box.content-block
%h3.commit-title
@@ -61,4 +59,4 @@
= preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:javascript
- $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
+ $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
index 82aac1fbd15..2b0c9a4b4de 100644
--- a/app/views/projects/commit/branches.html.haml
+++ b/app/views/projects/commit/branches.html.haml
@@ -3,7 +3,6 @@
- branch = commit_default_branch(@project, @branches)
= link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do
%span.label.label-gray
- %i.fa.fa-code-fork
= branch
- if @branches.any? || @tags.any?
= link_to("#", class: "js-details-expand") do
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 7a5b0d993db..d1bd76ab529 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -2,7 +2,8 @@
= nav_link(controller: [:commit, :commits]) do
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits
- %span.badge= number_with_delimiter(@repository.commit_count)
+ %span.badge
+ = number_with_delimiter(@repository.commit_count)
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index bcdb09208aa..088eaa28013 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -6,7 +6,7 @@
= render "head"
-.gray-content-block.second-block
+.row-content-block.second-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 02be5a2d07f..5e188dd0f3c 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
Compare branches, tags or commit ranges.
%br
Fill input field with commit id like
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index da731f28bb6..62525168239 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -3,7 +3,7 @@
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
= render "form"
- if @commits.present?
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 52d093871b4..1a2e59752fe 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,7 +7,7 @@
= render "home_panel"
-.gray-content-block.second-block.center
+.row-content-block.second-block.center
%h3.page-title
The repository for this project is empty
- if can?(current_user, :push_code, @project)
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index 6fa77cc10c6..9f05be9982b 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -1,7 +1,7 @@
- page_title "Continuous Integration", "Graphs"
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.oneline
A collection of graphs for Continuous Integration
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index fc465ab273b..da9f648cc9c 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index a7fab5b6d72..ebecab1dbfc 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.oneline
Programming languages used in this repository
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 882e7d6b6ee..ad4a932d391 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 6f1ee209430..36c1d69f060 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -16,6 +16,11 @@
= f.label :url, "URL", class: "label-light"
= f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json"
.form-group
+ = f.label :token, "Secret Token", class: 'label-light'
+ = f.text_field :token, class: "form-control", placeholder: ''
+ %p.help-block
+ Use this token to validate received payloads
+ .form-group
= f.label :url, "Trigger", class: "label-light"
%div
= f.check_box :push_events, class: "pull-left"
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 6027fb23360..a8a8caf7280 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -10,7 +10,7 @@
.panel-body
%pre
:preserve
- #{@project.import_error.try(:strip)}
+ #{sanitize_repo_path(@project.import_error)}
= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
= render "shared/import_form", f: f
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 56543ccd062..6ec84660157 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -24,15 +24,15 @@
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do
- = icon('trash-o')
- Delete
-
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
= icon('pencil-square-o')
Edit
-.detail-page-description.milestone-detail.second-block
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+ = icon('trash-o')
+ Delete
+
+.detail-page-description.milestone-detail
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 28a617538b5..c609c505def 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 6f0b32aa165..0d59cec322c 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.oneline
.title
Release notes for tag
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index d854ac21725..74feb9e3282 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,7 +12,7 @@
= render 'projects/last_push'
= render "home_panel"
-.project-stats.gray-content-block.second-block
+.project-stats.row-content-block.second-block
%ul.nav
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 4af963e14da..103ff447464 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Snippets"
= render "header_title"
-.gray-content-block.top-block
+.row-content-block.top-block
.pull-right
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
= icon('plus')
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 760347de0a9..dc6ece30dd2 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
- if can? current_user, :push_code, @project
.pull-right
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 1dc9b799a95..9c916fd02de 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.pull-right
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index dd27ea2b11b..ba3f2cadc48 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'nav'
-.gray-content-block
+.row-content-block
%span.oneline
Git access for
%strong= @project_wiki.path_with_namespace
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 711337f308e..252c37532e1 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,7 +1,7 @@
- if @search_objects.empty?
= render partial: "search/results/empty"
- else
- .gray-content-block
+ .row-content-block
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 34241cd8aad..b0fc60573f7 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -7,7 +7,7 @@
Confirmation required
.modal-body
- %p.cred.lead.js-confirm-text
+ %p.text-danger.js-confirm-text
%p
This action can lead to data loss.
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index fc1101646fb..9474462cbd1 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,5 +1,5 @@
.issues-filters
- .issues-details-filters.gray-content-block.second-block
+ .issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
@@ -48,7 +48,7 @@
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
- if !@labels.nil?
- .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
+ .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
- if @labels.any?
= render "shared/labels_row", labels: @labels
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 18b091df39b..5c52cc6d1da 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -116,7 +116,7 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
-.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
+.row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if issuable.new_record?
= f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index cab8743a077..7ff947a51db 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -24,7 +24,7 @@
- else
= link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.milestone-detail
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 3c445f67236..e65b1814872 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -20,6 +20,6 @@
- else
= render "snippets/actions"
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.row-content-block.second-block
%h2.title
= markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
index cfd11e45b6a..94d4dd4fa7d 100644
--- a/app/views/sherlock/file_samples/show.html.haml
+++ b/app/views/sherlock/file_samples/show.html.haml
@@ -3,7 +3,7 @@
- header_title t('sherlock.title'), sherlock_transactions_path
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml
index 83f61ce4b07..fc2863dca8e 100644
--- a/app/views/sherlock/queries/show.html.haml
+++ b/app/views/sherlock/queries/show.html.haml
@@ -9,7 +9,7 @@
%a(href="#tab-backtrace" data-toggle="tab")
= t('sherlock.backtrace')
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml
index 010e1a2a902..da969c02765 100644
--- a/app/views/sherlock/transactions/index.html.haml
+++ b/app/views/sherlock/transactions/index.html.haml
@@ -1,7 +1,7 @@
- page_title t('sherlock.title')
- header_title t('sherlock.title'), sherlock_transactions_path
-.gray-content-block
+.row-content-block
.pull-right
= link_to(destroy_all_sherlock_transactions_path,
class: 'btn btn-danger',
diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml
index 9d4b0b2724c..8aa6b437d95 100644
--- a/app/views/sherlock/transactions/show.html.haml
+++ b/app/views/sherlock/transactions/show.html.haml
@@ -16,7 +16,7 @@
%span.badge
#{@transaction.file_samples.length}
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transactions_path, class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0dff27f9654..3c0b89c6741 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -85,7 +85,7 @@
%div{ class: container_class }
.tab-content
#activity.tab-pane
- .gray-content-block.calender-block.white.second-block.hidden-xs
+ .row-content-block.calender-block.white.second-block.hidden-xs
%div{ class: container_class }
.user-calendar{data: {href: user_calendar_path}}
%h4.center.light
@@ -98,7 +98,7 @@
#groups.tab-pane
- # This tab is always loaded via AJAX
- #contributed.contributed-projects.tab-pane
+ #contributed.tab-pane
- # This tab is always loaded via AJAX
#projects.tab-pane
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 59e12798691..4beb8746444 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,16 +15,16 @@
- if current_user
:javascript
- var get_emojis_url = "#{emojis_path}";
- var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
- var noteable_type = "#{votable.class.name.underscore}";
- var noteable_id = "#{votable.id}";
+ var getEmojisUrl = "#{emojis_path}";
+ var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
+ var noteableType = "#{votable.class.name.underscore}";
+ var noteableId = "#{votable.id}";
var unicodes = #{AwardEmoji.unicode.to_json};
- window.awards_handler = new AwardsHandler(
- get_emojis_url,
- post_emoji_url,
- noteable_type,
- noteable_id,
+ window.awardsHandler = new AwardsHandler(
+ getEmojisUrl,
+ postEmojiUrl,
+ noteableType,
+ noteableId,
unicodes
);
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
index a76729e3c74..f2d12ba5a7d 100644
--- a/app/workers/repository_check/single_repository_worker.rb
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -1,9 +1,9 @@
module RepositoryCheck
class SingleRepositoryWorker
include Sidekiq::Worker
-
+
sidekiq_options retry: false
-
+
def perform(project_id)
project = Project.find(project_id)
project.update_columns(
@@ -11,20 +11,32 @@ module RepositoryCheck
last_repository_check_at: Time.now,
)
end
-
+
private
-
+
def check(project)
- repositories = [project.repository]
- repositories << project.wiki.repository if project.wiki_enabled?
- # Use 'map do', not 'all? do', to prevent short-circuiting
- repositories.map { |repository| git_fsck(repository.path_to_repo) }.all?
+ if !git_fsck(project.repository)
+ false
+ elsif project.wiki_enabled?
+ # Historically some projects never had their wiki repos initialized;
+ # this happens on project creation now. Let's initialize an empty repo
+ # if it is not already there.
+ begin
+ project.create_wiki
+ rescue Rugged::RepositoryError
+ end
+
+ git_fsck(project.wiki.repository)
+ else
+ true
+ end
end
-
- def git_fsck(path)
+
+ def git_fsck(repository)
+ path = repository.path_to_repo
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
-
+
if status.zero?
true
else
diff --git a/bin/background_jobs b/bin/background_jobs
index 1f67d732949..25a578a1c49 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
+ exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
diff --git a/bin/web b/bin/web
index 03fe7a6354b..ecd0bbd10b0 100755
--- a/bin/web
+++ b/bin/web
@@ -19,12 +19,12 @@ get_unicorn_pid()
start()
{
- $unicorn_cmd -D
+ exec $unicorn_cmd -D
}
start_foreground()
{
- $unicorn_cmd
+ exec $unicorn_cmd
}
stop()
diff --git a/config/application.rb b/config/application.rb
index abe22691ad1..d4b86bb38bb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -32,7 +32,30 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables, :import_url)
+ #
+ # Parameters filtered:
+ # - Password (:password, :password_confirmation)
+ # - Private tokens (:private_token)
+ # - Two-factor tokens (:otp_attempt)
+ # - Repo/Project Import URLs (:import_url)
+ # - Build variables (:variables)
+ # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
+ # - Webhook URLs (:hook)
+ # - Sentry DSN (:sentry_dsn)
+ # - Deploy keys (:key)
+ config.filter_parameters += %i(
+ certificate
+ encrypted_key
+ hook
+ import_url
+ key
+ otp_attempt
+ password
+ password_confirmation
+ private_token
+ sentry_dsn
+ variables
+ )
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e55ca6f9c6b..cbb7c656fe7 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -152,7 +152,6 @@ production: &base
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
- enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb
new file mode 100644
index 00000000000..05a1852cdbd
--- /dev/null
+++ b/config/initializers/devise_async.rb
@@ -0,0 +1 @@
+Devise::Async.backend = :sidekiq
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 283936d0efc..b2d08d87bac 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -61,12 +61,30 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const)
end
- Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
- const = File.basename(path, '.rb').camelize.constantize
-
- config.instrument_instance_methods(const)
+ # Path to search => prefix to strip from constant
+ paths_to_instrument = {
+ ['app', 'finders'] => ['app', 'finders'],
+ ['app', 'mailers', 'emails'] => ['app', 'mailers'],
+ ['app', 'services', '**'] => ['app', 'services'],
+ ['lib', 'gitlab', 'diff'] => ['lib'],
+ ['lib', 'gitlab', 'email', 'message'] => ['lib']
+ }
+
+ paths_to_instrument.each do |(path, prefix)|
+ prefix = Rails.root.join(*prefix)
+
+ Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
+ path = Pathname.new(file_path).relative_path_from(prefix)
+ const = path.to_s.sub('.rb', '').camelize.constantize
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
+ end
end
+ config.instrument_methods(Premailer::Adapter::Nokogiri)
+ config.instrument_instance_methods(Premailer::Adapter::Nokogiri)
+
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
@@ -97,17 +115,6 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor)
- # Instrument all service classes
- services = Rails.root.join('app', 'services')
-
- Dir[services.join('**', '*.rb')].each do |file_path|
- path = Pathname.new(file_path).relative_path_from(services)
- const = path.to_s.sub('.rb', '').camelize.constantize
-
- config.instrument_methods(const)
- config.instrument_instance_methods(const)
- end
-
# Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki)
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index e87899b2d5c..74fef7cadfe 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -15,6 +15,9 @@ if Rails.env.production?
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
config.release = Gitlab::REVISION
+
+ # Sanitize fields based on those sanitized from Rails.
+ config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
end
end
end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 88cb859871c..599dabb9e50 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -22,7 +22,7 @@ else
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
- expire_after: Settings.gitlab['session_expire_delay'] * 60,
+ expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
)
end
diff --git a/config/routes.rb b/config/routes.rb
index 5b48819dd9d..bfc6818a8d8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -216,8 +216,6 @@ Rails.application.routes.draw do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
- delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
-
member do
get :projects
get :keys
@@ -227,12 +225,14 @@ Rails.application.routes.draw do
put :unblock
put :unlock
put :confirm
- post 'impersonate' => 'impersonation#create'
+ post :impersonate
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
+ resource :impersonation, only: :destroy
+
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 0825776ffaa..87fb8e3300d 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,6 +1,9 @@
Gitlab::Seeder.quiet do
+ # Limit the number of merge requests per project to avoid long seeds
+ MAX_NUM_MERGE_REQUESTS = 10
+
Project.all.reject(&:empty_repo?).each do |project|
- branches = project.repository.branch_names
+ branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
branches.each do |branch_name|
break if branches.size < 2
diff --git a/db/migrate/20160413115152_add_token_to_web_hooks.rb b/db/migrate/20160413115152_add_token_to_web_hooks.rb
new file mode 100644
index 00000000000..f04225068cd
--- /dev/null
+++ b/db/migrate/20160413115152_add_token_to_web_hooks.rb
@@ -0,0 +1,5 @@
+class AddTokenToWebHooks < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :token, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bf46028d23f..7ea16e21358 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -70,16 +70,16 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "recaptcha_site_key"
t.string "recaptcha_private_key"
t.integer "metrics_port", default: 8089
+ t.boolean "akismet_enabled", default: false
+ t.string "akismet_api_key"
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
- t.boolean "akismet_enabled", default: false
- t.string "akismet_api_key"
t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility"
t.boolean "repository_checks_enabled", default: false
- t.integer "metrics_packet_size", default: 1
t.text "shared_runners_text"
+ t.integer "metrics_packet_size", default: 1
end
create_table "audit_events", force: :cascade do |t|
@@ -426,10 +426,10 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
- t.integer "moved_to_id"
t.boolean "confidential", default: false
t.datetime "deleted_at"
t.date "due_date"
+ t.integer "moved_to_id"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -716,8 +716,8 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.integer "project_id"
t.text "data"
t.text "encrypted_credentials"
- t.text "encrypted_credentials_iv"
- t.text "encrypted_credentials_salt"
+ t.string "encrypted_credentials_iv"
+ t.string "encrypted_credentials_salt"
end
create_table "projects", force: :cascade do |t|
@@ -817,9 +817,9 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "type"
t.string "title"
t.integer "project_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "active", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "active", default: false, null: false
t.text "properties"
t.boolean "template", default: false
t.boolean "push_events", default: true
@@ -1026,6 +1026,7 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
t.boolean "wiki_page_events", default: false, null: false
+ t.string "token"
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 6341440c58b..57c2e1d9b87 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -12,6 +12,8 @@ GET /projects/:id/repository/commits
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
+| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
+| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 7aa1c2155bf..a6b5b1787fd 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
```json
[
@@ -73,7 +73,7 @@ GET /projects/:id/issues/:issue_id/notes/:note_id
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project issue
+- `issue_id` (required) - The IID of a project issue (not ID)
- `note_id` (required) - The ID of an issue note
### Create new issue note
@@ -87,7 +87,7 @@ POST /projects/:id/issues/:issue_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
- `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
@@ -102,7 +102,7 @@ PUT /projects/:id/issues/:issue_id/notes/:note_id
Parameters:
- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of an issue
+- `issue_id` (required) - The IID of an issue (not ID)
- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
@@ -120,7 +120,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
-| `issue_id` | integer | yes | The ID of an issue |
+| `issue_id` | integer | yes | The IID of an issue |
| `note_id` | integer | yes | The ID of a note |
```bash
diff --git a/doc/api/users.md b/doc/api/users.md
index 7d2b4897cff..7e848586dbd 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -20,6 +20,7 @@ GET /users
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "web_url": "http://localhost:3000/u/john_smith"
},
{
"id": 2,
@@ -27,6 +28,7 @@ GET /users
"name": "Jack Smith",
"state": "blocked",
"avatar_url": "http://gravatar.com/../e32131cd8.jpeg",
+ "web_url": "http://localhost:3000/u/jack_smith"
}
]
```
@@ -45,21 +47,31 @@ GET /users
"email": "john@example.com",
"name": "John Smith",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
+ "location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "john.smith",
- "provider": "provider_name",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john.smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
- "current_sign_in_at": "2014-03-19T13:12:15Z",
- "two_factor_enabled": true
+ "can_create_project": true,
+ "two_factor_enabled": true,
+ "external": false
},
{
"id": 2,
@@ -67,24 +79,27 @@ GET /users
"email": "jack@example.com",
"name": "Jack Smith",
"state": "blocked",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
+ "web_url": "http://localhost:3000/u/jack_smith",
"created_at": "2012-05-23T08:01:01Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "jack.smith",
- "provider": "provider_name",
+ "last_sign_in_at": null,
+ "confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1,
"color_scheme_id": 3,
- "is_admin": false,
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
- "can_create_group": true,
- "can_create_project": true,
"projects_limit": 100,
"current_sign_in_at": "2014-03-19T17:54:13Z",
- "two_factor_enabled": false
+ "identities": [],
+ "can_create_group": true,
+ "can_create_project": true,
+ "two_factor_enabled": true,
+ "external": false
}
]
```
@@ -124,6 +139,7 @@ Parameters:
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
@@ -152,23 +168,31 @@ Parameters:
"email": "john@example.com",
"name": "John Smith",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
- "confirmed_at": "2012-05-23T08:00:58Z",
- "last_sign_in_at": "2015-03-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "john.smith",
- "provider": "provider_name",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john.smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
"can_create_project": true,
- "projects_limit": 100
+ "two_factor_enabled": true,
+ "external": false
}
```
@@ -261,21 +285,33 @@ GET /user
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
- "private_token": "dd34asd13as",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john_smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
"can_create_project": true,
- "projects_limit": 100
+ "two_factor_enabled": true,
+ "external": false,
+ "private_token": "dd34asd13as"
}
```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 5fb086b1dd9..ca52a483a59 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -41,9 +41,9 @@ GitLab Runner then executes build scripts as `gitlab-runner` user.
--description "My Runner"
```
-2. Install Docker on server.
+2. Install Docker Engine on server.
- For more information how to install Docker on different systems checkout the [Supported installations](https://docs.docker.com/installation/).
+ For more information how to install Docker Engine on different systems checkout the [Supported installations](https://docs.docker.com/engine/installation/).
3. Add `gitlab-runner` user to `docker` group:
@@ -151,4 +151,4 @@ In order to do that follow the steps:
An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
-[docker-cap]: https://docs.docker.com/reference/run/#runtime-privilege-and-linux-capabilities
+[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 84212fb3c61..56ac2195c49 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -64,7 +64,7 @@ You can see some widely used services examples in the relevant documentation of
### How is service linked to the build
To better understand how the container linking works, read
-[Linking containers together](https://docs.docker.com/userguide/dockerlinks/).
+[Linking containers together][linking-containers].
To summarize, if you add `mysql` as service to your application, the image will
then be used to create a container that is linked to the build container.
@@ -273,7 +273,7 @@ creation.
[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
-[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
-[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/
-[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/
+[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/
+[postgres-hub]: https://hub.docker.com/r/_/postgres/
+[mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index cc059dc4376..61294be599d 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -4,12 +4,12 @@
- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
- [Test a Clojure application](test-clojure-application.md)
-- [Using `dpl` as deployment tool](deployment/README.md)
+- [Using `dpl` as deployment tool](../deployment/README.md)
- Help your favorite programming language and GitLab by sending a merge request
with a guide for that language.
## Outside the documentation
-- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index db077927126..26953014502 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -60,7 +60,7 @@ docker-php-ext-install pdo_mysql
You might wonder what `docker-php-ext-install` is. In short, it is a script
provided by the official php docker image that you can use to easilly install
extensions. For more information read the the documentation at
-<https://hub.docker.com/_/php/>.
+<https://hub.docker.com/r/_/php/>.
Now that we created the script that contains all prerequisites for our build
environment, let's add it in `.gitlab-ci.yml`:
@@ -92,7 +92,7 @@ Finally, commit your files and push them to GitLab to see your build succeeding
The final `.gitlab-ci.yml` should look similar to this:
```yaml
-# Select image from https://hub.docker.com/_/php/
+# Select image from https://hub.docker.com/r/_/php/
image: php:5.6
before_script:
@@ -278,7 +278,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[php-hub]: https://hub.docker.com/_/php/
+[php-hub]: https://hub.docker.com/r/_/php/
[phpenv]: https://github.com/phpenv/phpenv
[phpenv-installation]: https://github.com/phpenv/phpenv#installation
[php-example-repo]: https://gitlab.com/gitlab-examples/php
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index a236da53fe9..e4d3970deac 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -8,7 +8,7 @@ This is what the `.gitlab-ci.yml` file looks like for this project:
```yaml
test:
script:
- # this configures django application to use attached postgres database that is run on `postgres` host
+ # this configures Django application to use attached postgres database that is run on `postgres` host
- export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
- apt-get update -qy
- apt-get install -y python-dev python-pip
@@ -37,7 +37,7 @@ production:
```
This project has three jobs:
-1. `test` - used to test rails application,
+1. `test` - used to test Django application,
2. `staging` - used to automatically deploy staging environment every push to `master` branch
3. `production` - used to automatically deploy production environmnet for every created tag
@@ -61,12 +61,12 @@ gitlab-ci-multi-runner register \
--non-interactive \
--url "https://gitlab.com/ci/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "python-3.2" \
+ --description "python-3.5" \
--executor "docker" \
- --docker-image python:3.2 \
+ --docker-image python:3.5 \
--docker-postgres latest
```
-With the command above, you create a runner that uses [python:3.2](https://registry.hub.docker.com/u/library/python/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [python:3.5](https://hub.docker.com/r/_/python/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index f5645d586ae..08c10d391ea 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -1,5 +1,5 @@
## Test and Deploy a ruby application
-This example will guide you how to run tests in your Ruby application and deploy it automatically as Heroku application.
+This example will guide you how to run tests in your Ruby on Rails application and deploy it automatically as Heroku application.
You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://gitlab.com/ayufan/ruby-getting-started/builds?scope=all).
@@ -32,7 +32,7 @@ production:
```
This project has three jobs:
-1. `test` - used to test rails application,
+1. `test` - used to test Rails application,
2. `staging` - used to automatically deploy staging environment every push to `master` branch
3. `production` - used to automatically deploy production environmnet for every created tag
@@ -62,6 +62,6 @@ gitlab-ci-multi-runner register \
--docker-postgres latest
```
-With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [ruby:2.2](https://hub.docker.com/r/_/ruby/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index c66d77122b2..aaf3aa77837 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -16,7 +16,7 @@ services:
- mysql:latest
variables:
- # Configure mysql environment variables (https://hub.docker.com/_/mysql/)
+ # Configure mysql environment variables (https://hub.docker.com/r/_/mysql/)
MYSQL_DATABASE: el_duderino
MYSQL_ROOT_PASSWORD: mysql_strong_password
```
@@ -114,5 +114,5 @@ available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-mysql]: https://hub.docker.com/_/mysql/
+[hub-mysql]: https://hub.docker.com/r/_/mysql/
[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index 17d21dbda1c..f787cc0a124 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -110,5 +110,5 @@ available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-pg]: https://hub.docker.com/_/postgres/
+[hub-pg]: https://hub.docker.com/r/_/postgres/
[postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index b281e8f9f60..80705024d2f 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -65,5 +65,5 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-redis]: https://hub.docker.com/_/redis/
+[hub-redis]: https://hub.docker.com/r/_/redis/
[redis-example-repo]: https://gitlab.com/gitlab-examples/redis
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 7f825e6a065..7c0fb225dac 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -57,7 +57,7 @@ before_script:
# WARNING: Use this only with the Docker executor, if you use it with shell
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+ - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
```
As a final step, add the _public_ key from the one you created earlier to the
diff --git a/doc/development/README.md b/doc/development/README.md
index 3f3ef068f96..aa7d54c01d0 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,6 +8,7 @@
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
diff --git a/doc/development/performance.md b/doc/development/performance.md
new file mode 100644
index 00000000000..fb37b3a889c
--- /dev/null
+++ b/doc/development/performance.md
@@ -0,0 +1,258 @@
+# Performance Guidelines
+
+This document describes various guidelines to follow to ensure good and
+consistent performance of GitLab.
+
+## Workflow
+
+The process of solving performance problems is roughly as follows:
+
+1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue
+ tracker), create one if there isn't. See [#15607][#15607] for an example.
+2. Measure the performance of the code in a production environment such as
+ GitLab.com (see the [Tooling](#tooling) section below). Performance should be
+ measured over a period of _at least_ 24 hours.
+3. Add your findings based on the measurement period (screenshots of graphs,
+ timings, etc) to the issue mentioned in step 1.
+4. Solve the problem.
+5. Create a merge request, assign the "performance" label and ping the right
+ people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]).
+6. Once a change has been deployed make sure to _again_ measure for at least 24
+ hours to see if your changes have any impact on the production environment.
+7. Repeat until you're done.
+
+When providing timings make sure to provide:
+
+* The 95th percentile
+* The 99th percentile
+* The mean
+
+When providing screenshots of graphs, make sure that both the X and Y axes and
+the legend are clearly visible. If you happen to have access to GitLab.com's own
+monitoring tools you should also provide a link to any relevant
+graphs/dashboards.
+
+## Tooling
+
+GitLab provides two built-in tools to aid the process of improving performance:
+
+* [Sherlock](doc/development/profiling.md#sherlock)
+* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md)
+
+GitLab employees can use GitLab.com's performance monitoring systems located at
+<http://performance.gitlab.net>, this requires you to log in using your
+`@gitlab.com` Email address. Non-GitLab employees are advised to set up their
+own InfluxDB + Grafana stack.
+
+## Benchmarks
+
+Benchmarks are almost always useless. Benchmarks usually only test small bits of
+code in isolation and often only measure the best case scenario. On top of that,
+benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the
+library. After all there's little benefit to an author publishing a benchmark
+that shows they perform worse than their competitors.
+
+Benchmarks are only really useful when you need a rough (emphasis on "rough")
+understanding of the impact of your changes. For example, if a certain method is
+slow a benchmark can be used to see if the changes you're making have any impact
+on the method's performance. However, even when a benchmark shows your changes
+improve performance there's no guarantee the performance also improves in a
+production environment.
+
+When writing benchmarks you should almost always use
+[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark`
+module that comes with the standard library is rarely useful as it runs either a
+single iteration (when using `Benchmark.bm`) or two iterations (when using
+`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a
+video streaming in the background) can very easily skew the benchmark
+statistics.
+
+Another problem with the `Benchmark` module is that it displays timings, not
+iterations. This means that if a piece of code completes in a very short period
+of time it can be very difficult to compare the timings before and after a
+certain change. This in turn leads to patterns such as the following:
+
+```ruby
+Benchmark.bmbm(10) do |bench|
+ bench.report 'do something' do
+ 100.times do
+ ... work here ...
+ end
+ end
+end
+```
+
+This however leads to the question: how many iterations should we run to get
+meaningful statistics?
+
+The benchmark-ips Gem basically takes care of all this and much more, and as a
+result of this should be used instead of the `Benchmark` module.
+
+In short:
+
+1. Don't trust benchmarks you find on the internet.
+2. Never make claims based on just benchmarks, always measure in production to
+ confirm your findings.
+3. X being N times faster than Y is meaningless if you don't know what impact it
+ will actually have on your production environment.
+4. A production environment is the _only_ benchmark that always tells the truth
+ (unless your performance monitoring systems are not set up correctly).
+5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's
+ `Benchmark` module.
+
+## Importance of Changes
+
+When working on performance improvements, it's important to always ask yourself
+the question "How important is it to improve the performance of this piece of
+code?". Not every piece of code is equally important and it would be a waste to
+spend a week trying to improve something that only impacts a tiny fraction of
+our users. For example, spending a week trying to squeeze 10 milliseconds out of
+a method is a waste of time when you could have spent a week squeezing out 10
+seconds elsewhere.
+
+There is no clear set of steps that you can follow to determine if a certain
+piece of code is worth optimizing. The only two things you can do are:
+
+1. Think about what the code does, how it's used, how many times it's called and
+ how much time is spent in it relative to the total execution time (e.g., the
+ total time spent in a web request).
+2. Ask others (preferably in the form of an issue).
+
+Some examples of changes that aren't really important/worth the effort:
+
+* Replacing double quotes with single quotes.
+* Replacing usage of Array with Set when the list of values is very small.
+* Replacing library A with library B when both only take up 0.1% of the total
+ execution time.
+* Calling `freeze` on every string (see [String Freezing](#string-freezing)).
+
+## Slow Operations & Sidekiq
+
+Slow operations (e.g. merging branches) or operations that are prone to errors
+(using external APIs) should be performed in a Sidekiq worker instead of
+directly in a web request as much as possible. This has numerous benefits such
+as:
+
+1. An error won't prevent the request from completing.
+2. The process being slow won't affect the loading time of a page.
+3. In case of a failure it's easy to re-try the process (Sidekiq takes care of
+ this automatically).
+4. By isolating the code from a web request it will hopefully be easier to test
+ and maintain.
+
+It's especially important to use Sidekiq as much as possible when dealing with
+Git operations as these operations can take quite some time to complete
+depending on the performance of the underlying storage system.
+
+## Git Operations
+
+Care should be taken to not run unnecessary Git operations. For example,
+retrieving the list of branch names using `Repository#branch_names` can be done
+without an explicit check if a repository exists or not. In other words, instead
+of this:
+
+```ruby
+if repository.exists?
+ repository.branch_names.each do |name|
+ ...
+ end
+end
+```
+
+You can just write:
+
+```ruby
+repository.branch_names.each do |name|
+ ...
+end
+```
+
+## Caching
+
+Operations that will often return the same result should be cached using Redis,
+in particular Git operations. When caching data in Redis, make sure the cache is
+flushed whenever needed. For example, a cache for the list of tags should be
+flushed whenever a new tag is pushed or a tag is removed.
+
+When adding cache expiration code for repositories, this code should be placed
+in one of the before/after hooks residing in the Repository class. For example,
+if a cache should be flushed after importing a repository this code should be
+added to `Repository#after_import`. This ensures the cache logic stays within
+the Repository class instead of leaking into other classes.
+
+When caching data, make sure to also memoize the result in an instance variable.
+While retrieving data from Redis is much faster than raw Git operations, it still
+has overhead. By caching the result in an instance variable, repeated calls to
+the same method won't end up retrieving data from Redis upon every call. When
+memoizing cached data in an instance variable, make sure to also reset the
+instance variable when flushing the cache. An example:
+
+
+```ruby
+def first_branch
+ @first_branch ||= cache.fetch(:first_branch) { branches.first }
+end
+
+def expire_first_branch_cache
+ cache.expire(:first_branch)
+ @first_branch = nil
+end
+```
+
+## Anti-Patterns
+
+This is a collection of [anti-patterns][anti-pattern] that should be avoided
+unless these changes have a measurable, significant and positive impact on
+production environments.
+
+### String Freezing
+
+In recent Ruby versions calling `freeze` on a String leads to it being allocated
+only once and re-used. For example, on Ruby 2.3 this will only allocate the
+"foo" String once:
+
+```ruby
+10.times do
+ 'foo'.freeze
+end
+```
+
+Blindly adding a `.freeze` call to every String is an anti-pattern that should
+be avoided unless one can prove (using production data) the call actually has a
+positive impact on performance.
+
+This feature of Ruby wasn't really meant to make things faster directly, instead
+it was meant to reduce the number of allocations. Depending on the size of the
+String and how frequently it would be allocated (before the `.freeze` call was
+added), this _may_ make things faster, but there's no guarantee it will.
+
+Another common flavour of this is to not only freeze a String, but also assign
+it to a constant, for example:
+
+```ruby
+SOME_CONSTANT = 'foo'.freeze
+
+9000.times do
+ SOME_CONSTANT
+end
+```
+
+The only reason you should be doing this is to prevent somebody from mutating
+the global String. However, since you can just re-assign constants in Ruby
+there's nothing stopping somebody from doing this elsewhere in the code:
+
+```ruby
+SOME_CONSTANT = 'bar'
+```
+
+### Moving Allocations to Constants
+
+Storing an object as a constant so you only allocate it once _may_ improve
+performance, but there's no guarantee this will. Looking up constants has an
+impact on runtime performance, and as such, using a constant instead of
+referencing an object directly may even slow code down.
+
+[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
+[yorickpeterse]: https://gitlab.com/u/yorickpeterse
+[joshfng]: https://gitlab.com/u/joshfng
+[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 672e3fb4649..33eed29ba5c 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -64,6 +64,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at
methods.
- Use `context` to test branching logic.
- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
+- Don't supply the `:each` argument to hooks since it's the default.
- Prefer `not_to` to `to_not`.
- Try to match the ordering of tests to the ordering within the class.
- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
diff --git a/doc/install/installation.md b/doc/install/installation.md
index e721e70a596..e3af3022262 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -157,22 +157,64 @@ Create a `git` user for GitLab:
## 5. Database
-We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1.
+We recommend using a PostgreSQL database. For MySQL check the
+[MySQL setup guide](database_mysql.md).
- # Install the database packages
- sudo apt-get install -y postgresql postgresql-client libpq-dev
+> **Note**: because we need to make use of extensions you need at least pgsql 9.1.
- # Create a user for GitLab
+1. Install the database packages:
+
+ ```bash
+ sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
+ ```
+
+1. Create a database user for GitLab:
+
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
+ ```
+
+1. Create the GitLab production database and grant all privileges on database:
- # Create the GitLab production database & grant all privileges on database
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
+ ```
+
+1. Create the `pg_trgm` extension (required for GitLab 8.6+):
+
+ ```bash
+ sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
+ ```
+
+1. Try connecting to the new database with the new user:
- # Try connecting to the new database with the new user
+ ```bash
sudo -u git -H psql -d gitlabhq_production
+ ```
+
+1. Check if the `pg_trgm` extension is enabled:
+
+ ```bash
+ SELECT true AS enabled
+ FROM pg_available_extensions
+ WHERE name = 'pg_trgm'
+ AND installed_version IS NOT NULL;
+ ```
+
+ If the extension is enabled this will produce the following output:
- # Quit the database session
+ ```
+ enabled
+ ---------
+ t
+ (1 row)
+ ```
+
+1. Quit the database session:
+
+ ```bash
gitlabhq_production> \q
+ ```
## 6. Redis
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 58f409746cd..df8e8bdc476 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -64,7 +64,10 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### Memory
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
-With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
+The operating system and any other running applications will also be using memory
+so keep in mind that you need at least 2GB available before running GitLab. With
+less memory GitLab will give strange errors during the reconfigure run and 500
+errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow
@@ -77,6 +80,10 @@ With less memory GitLab will give strange errors during the reconfigure run and
- 128GB RAM supports up to 32,000 users
- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+We recommend having at least 1GB of swap on your server, even if you currently have
+enough available RAM. Having swap will help reduce the chance of errors occuring
+if your available memory changes.
+
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
## Gitlab Runner
diff --git a/doc/integration/github.md b/doc/integration/github.md
index e1f9242fd0e..e7497e475c9 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -9,7 +9,9 @@ GitHub will generate an application ID and secret key for you to use.
1. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
-1. Select "Applications" in the left menu.
+1. Select "OAuth applications" in the left menu.
+
+1. If you already have applications listed, switch to the "Developer applications" tab.
1. Select "Register new application".
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
index 90e99302210..771584268d9 100644
--- a/doc/monitoring/performance/gitlab_configuration.md
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -37,4 +37,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
index 63aa03985ef..c30cd2950d8 100644
--- a/doc/monitoring/performance/influxdb_configuration.md
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -181,7 +181,7 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
index d31b3788f36..41861860b6d 100644
--- a/doc/monitoring/performance/influxdb_schema.md
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -85,4 +85,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 60729316cde..b4283a526f3 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -57,10 +57,10 @@ sudo -u git -H make
cd /home/git/gitlab
# PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+sudo -u git -H bundle install --without development test mysql --with postgres --deployment
# MySQL
-sudo -u git -H bundle install --without development test postgres --deployment
+sudo -u git -H bundle install --without development test postgres --with mysql --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
diff --git a/docker/README.md b/docker/README.md
index 7514d610aec..ee1f32adc26 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,7 +1,7 @@
# GitLab Docker images
-* The official GitLab Community Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
-* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ee/).
+* The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/).
+* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/).
* The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/)
* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
-* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#Build-Docker-image)
+* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#build-docker-image)
diff --git a/features/groups.feature b/features/groups.feature
index 419a5d3963d..49e939807b5 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -7,10 +7,6 @@ Feature: Groups
When I visit group "NonExistentGroup" page
Then page status code should be 404
- Scenario: I should have back to group button
- When I visit group "Owned" page
- Then I should see back to dashboard button
-
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index a167d259837..f5fddab357d 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- click_link 'Milestones'
+ page.within('.layout-nav') do
+ click_link 'Milestones'
+ end
end
step 'I should see group milestones index page has no milestones' do
@@ -84,7 +86,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I click on the "Labels" tab' do
- page.within('.nav-links') do
+ page.within('.content .nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index e5b7db4c5e3..483370f41c6 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup
include SharedUser
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
index eff4234a44a..912ba580efd 100644
--- a/features/steps/project/commits/tags.rb
+++ b/features/steps/project/commits/tags.rb
@@ -57,11 +57,11 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
end
step 'I should see new an error that tag ref is invalid' do
- expect(page).to have_content 'Invalid reference name'
+ expect(page).to have_content 'Target foo is invalid'
end
step 'I should see new an error that tag already exists' do
- expect(page).to have_content 'Tag already exists'
+ expect(page).to have_content 'Tag v1.0.0 already exists'
end
step "I visit tag 'v1.1.0' page" do
diff --git a/features/steps/user.rb b/features/steps/user.rb
index 3230234cb6d..b1d088f07f9 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -12,7 +12,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
user = User.find_by(name: 'John Doe')
project = contributed_project
- # Issue controbution
+ # Issue contribution
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(project, user, issue_params).execute
@@ -28,7 +28,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
end
step 'I should see contributed projects' do
- page.within '.contributed-projects' do
+ page.within '#contributed' do
expect(page).to have_content(@contributed_project.name)
end
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4544a41b1e3..93a3a5ce089 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -12,14 +12,20 @@ module API
# Parameters:
# id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # since (optional) - Only commits after or in this date will be returned
+ # until (optional) - Only commits before or in this date will be returned
# Example Request:
# GET /projects/:id/repository/commits
get ":id/repository/commits" do
+ datetime_attributes! :since, :until
+
page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ after = params[:since]
+ before = params[:until]
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before)
present commits, with: Entities::RepoCommit
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 5bbf721321d..40c967453fb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -183,6 +183,22 @@ module API
Gitlab::Access.options_with_owner.values.include? level.to_i
end
+ # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
+ # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - An array consisting of elements that must be parseable as dates from the params hash
+ def datetime_attributes!(*keys)
+ keys.each do |key|
+ begin
+ params[key] = Time.xmlschema(params[key]) if params[key].present?
+ rescue ArgumentError
+ message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
+ render_api_error!(message, 400)
+ end
+ end
+ end
+
def issuable_order_by
if params["order_by"] == 'updated_at'
'updated_at'
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 84b4d4cdd6d..132043cf3f7 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -105,7 +105,15 @@ module API
authorize! :read_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_title: @milestone.title,
+ state: 'all'
+ }
+
+ issues = IssuesFinder.new(current_user, finder_params).execute
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 22ce3c6a066..ce1bf0d26d2 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -11,6 +11,11 @@ module API
end
not_found!
end
+
+ def snippets_for_current_user
+ finder_params = { filter: :by_project, project: user_project }
+ SnippetsFinder.new.execute(current_user, finder_params)
+ end
end
# Get a project snippets
@@ -20,7 +25,7 @@ module API
# Example Request:
# GET /projects/:id/snippets
get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
end
# Get a project snippet
@@ -31,7 +36,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id
get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
present @snippet, with: Entities::ProjectSnippet
end
@@ -73,7 +78,7 @@ module API
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
@@ -97,7 +102,7 @@ module API
# DELETE /projects/:id/snippets/:snippet_id
delete ":id/snippets/:snippet_id" do
begin
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
@snippet.destroy
rescue
@@ -113,7 +118,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id/raw
get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
env['api.format'] = :txt
content_type 'text/plain'
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 5e2fb863a8f..132f9cd1966 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -79,24 +79,6 @@ module Gitlab
'rm-project', "#{name}.git"])
end
- # Add repository tag from passed ref
- #
- # path - project path with namespace
- # tag_name - new tag name
- # ref - HEAD for new tag
- # message - optional message for tag (annotated tag)
- #
- # Ex.
- # add_tag("gitlab/gitlab-ci", "v4.0", "master")
- # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message")
- #
- def add_tag(path, tag_name, ref, message = nil)
- cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
- #{tag_name} #{ref})
- cmd << message unless message.nil? || message.empty?
- Gitlab::Utils.system_silent(cmd)
- end
-
# Gc repository
#
# path - project path with namespace
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
index 9bb507b5edd..9b83292ef33 100644
--- a/lib/gitlab/bitbucket_import/client.rb
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -12,7 +12,7 @@ module Gitlab
token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret]
new(token, token_secret)
else
- raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{project.id}"
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 3ed1eec517c..6cb41239871 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -128,7 +128,7 @@ module Gitlab
action =
if project.protected_branch?(branch_name(ref))
protected_branch_action(oldrev, newrev, branch_name(ref))
- elsif protected_tag?(tag_name(ref))
+ elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref)
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
@@ -176,7 +176,7 @@ module Gitlab
end
def protected_tag?(tag_name)
- project.repository.tag_names.include?(tag_name)
+ project.repository.tag_exists?(tag_name)
end
def user_allowed?
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 7d58e53991a..7d679eaec6a 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -28,13 +28,26 @@ module Gitlab
end
def line_code
- if on_diff?
- Gitlab::Diff::LineCode.generate(raw_data.path, raw_data.position, 0)
- end
+ return unless on_diff?
+
+ parsed_lines = Gitlab::Diff::Parser.new.parse(diff_hunk.lines)
+ generate_line_code(parsed_lines.to_a.last)
+ end
+
+ def generate_line_code(line)
+ Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def on_diff?
- raw_data.path && raw_data.position
+ diff_hunk.present?
+ end
+
+ def diff_hunk
+ raw_data.diff_hunk
+ end
+
+ def file_path
+ raw_data.path
end
def note
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 67622f321a6..c8f12577112 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -66,7 +66,7 @@ module Gitlab
# This method provide a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
- commits = project.repository.commits(project.default_branch, nil, 3)
+ commits = project.repository.commits(project.default_branch, limit: 3)
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
build(project, user, commits.last.id, commits.first.id, ref, commits)
end
diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb
new file mode 100644
index 00000000000..b98589dff89
--- /dev/null
+++ b/lib/gitlab/sanitizers/svg.rb
@@ -0,0 +1,37 @@
+require_relative "svg/whitelist"
+
+module Gitlab
+ module Sanitizers
+ module SVG
+ def self.clean(data)
+ Loofah.xml_document(data).scrub!(Scrubber.new).to_s
+ end
+
+ class Scrubber < Loofah::Scrubber
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data-with-the-data-*-attributes
+ DATA_ATTR_PATTERN = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u
+
+ def scrub(node)
+ unless ALLOWED_ELEMENTS.include?(node.name)
+ node.unlink
+ else
+ node.attributes.each do |attr_name, attr|
+ valid_attributes = ALLOWED_ATTRIBUTES[node.name]
+
+ unless valid_attributes && valid_attributes.include?(attr_name)
+ if ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) &&
+ attr_name.start_with?('data-')
+ # Arbitrary data attributes are allowed. Verify that the attribute
+ # is a valid data attribute.
+ attr.unlink unless attr_name =~ DATA_ATTR_PATTERN
+ else
+ attr.unlink
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sanitizers/svg/whitelist.rb b/lib/gitlab/sanitizers/svg/whitelist.rb
new file mode 100644
index 00000000000..917e795b29e
--- /dev/null
+++ b/lib/gitlab/sanitizers/svg/whitelist.rb
@@ -0,0 +1,107 @@
+# Generated from:
+# SVG element list: https://www.w3.org/TR/SVG/eltindex.html
+# SVG Attribute list: https://www.w3.org/TR/SVG/attindex.html
+module Gitlab
+ module Sanitizers
+ module SVG
+ ALLOWED_ELEMENTS = %w[
+ a altGlyph altGlyphDef altGlyphItem animate
+ animateColor animateMotion animateTransform circle clipPath color-profile
+ cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer
+ feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
+ feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
+ feImage feMerge feMergeNode feMorphology feOffset fePointLight
+ feSpecularLighting feSpotLight feTile feTurbulence filter font font-face
+ font-face-format font-face-name font-face-src font-face-uri foreignObject
+ g glyph glyphRef hkern image line linearGradient marker mask metadata
+ missing-glyph mpath path pattern polygon polyline radialGradient rect
+ script set stop style svg switch symbol text textPath title tref tspan use
+ view vkern].freeze
+
+ ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS = %w[svg].freeze
+
+ ALLOWED_ATTRIBUTES = {
+ 'a' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage target text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'altGlyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'altGlyphDef' => %w[id xml:base xml:lang xml:space],
+ 'altGlyphItem' => %w[id xml:base xml:lang xml:space],
+ 'animate' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateColor' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateMotion' => %w[accumulate additive begin by calcMode dur end externalResourcesRequired fill from id keyPoints keySplines keyTimes max min onbegin onend onload onrepeat origin path repeatCount repeatDur requiredExtensions requiredFeatures restart rotate systemLanguage to values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateTransform' => %w[accumulate additive attributeName attributeType begin by calcMode dur end externalResourcesRequired fill from id keySplines keyTimes max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to type values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'circle' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events r requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'clipPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule clipPathUnits color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'color-profile' => %w[id local name rendering-intent xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'cursor' => %w[externalResourcesRequired id requiredExtensions requiredFeatures systemLanguage x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'defs' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'desc' => %w[class id style xml:base xml:lang xml:space],
+ 'ellipse' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'feBlend' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask mode opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feColorMatrix' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi values visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feComponentTransfer' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feComposite' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 k1 k2 k3 k4 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feConvolveMatrix' => %w[alignment-baseline baseline-shift bias class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display divisor dominant-baseline edgeMode enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelMatrix kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity order overflow pointer-events preserveAlpha result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style targetX targetY text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feDiffuseLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor diffuseConstant direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feDisplacementMap' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result scale shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xChannelSelector xml:base xml:lang xml:space y yChannelSelector],
+ 'feDistantLight' => %w[azimuth elevation id xml:base xml:lang xml:space],
+ 'feFlood' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feFuncA' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncB' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncG' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncR' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feGaussianBlur' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stdDeviation stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feImage' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events preserveAspectRatio result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'feMerge' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feMergeNode' => %w[id xml:base xml:lang xml:space],
+ 'feMorphology' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events radius result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feOffset' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'fePointLight' => %w[id x xml:base xml:lang xml:space y z],
+ 'feSpecularLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering specularConstant specularExponent stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feSpotLight' => %w[id limitingConeAngle pointsAtX pointsAtY pointsAtZ specularExponent x xml:base xml:lang xml:space y z],
+ 'feTile' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feTurbulence' => %w[alignment-baseline baseFrequency baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask numOctaves opacity overflow pointer-events result seed shape-rendering stitchTiles stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'filter' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events primitiveUnits shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'font' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x horiz-origin-y id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'font-face' => %w[accent-height alphabetic ascent bbox cap-height descent font-family font-size font-stretch font-style font-variant font-weight hanging id ideographic mathematical overline-position overline-thickness panose-1 slope stemh stemv strikethrough-position strikethrough-thickness underline-position underline-thickness unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical widths x-height xml:base xml:lang xml:space],
+ 'font-face-format' => %w[id string xml:base xml:lang xml:space],
+ 'font-face-name' => %w[id name xml:base xml:lang xml:space],
+ 'font-face-src' => %w[id xml:base xml:lang xml:space],
+ 'font-face-uri' => %w[id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'foreignObject' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'g' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'glyph' => %w[alignment-baseline arabic-form baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning lang letter-spacing lighting-color marker-end marker-mid marker-start mask opacity orientation overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'glyphRef' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'hkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space],
+ 'image' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'line' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode x1 x2 xml:base xml:lang xml:space y1 y2],
+ 'linearGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x1 x2 xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y1 y2],
+ 'marker' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask opacity orient overflow pointer-events preserveAspectRatio refX refY shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'mask' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask maskContentUnits maskUnits opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'metadata' => %w[id xml:base xml:lang xml:space],
+ 'missing-glyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'mpath' => %w[externalResourcesRequired id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'path' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pathLength pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'pattern' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow patternContentUnits patternTransform patternUnits pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi viewBox visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'polygon' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'polyline' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'radialGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight fx fy glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events r shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space],
+ 'rect' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'script' => %w[externalResourcesRequired id type xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'set' => %w[attributeName attributeType begin dur end externalResourcesRequired fill id max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'stop' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask offset opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'style' => %w[id media title type xml:base xml:lang xml:space],
+ 'svg' => %w[alignment-baseline baseProfile baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onabort onactivate onclick onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onresize onscroll onunload onzoom opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi version viewBox visibility width word-spacing writing-mode x xml:base xml:lang xml:space xmlns y zoomAndPan],
+ 'switch' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'symbol' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'text' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength transform unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'textPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask method onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering spacing startOffset stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space],
+ 'title' => %w[class id style xml:base xml:lang xml:space],
+ 'tref' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'tspan' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'use' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'view' => %w[externalResourcesRequired id preserveAspectRatio viewBox viewTarget xml:base xml:lang xml:space zoomAndPan],
+ 'vkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space]
+ }.freeze
+ end
+ end
+end
diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
new file mode 100644
index 00000000000..16bad4bd2bd
--- /dev/null
+++ b/lib/tasks/auto_annotate_models.rake
@@ -0,0 +1,44 @@
+if Rails.env.development?
+ task :set_annotation_options do
+ # You can override any of these by setting an environment variable of the
+ # same name.
+ Annotate.set_defaults(
+ 'routes' => 'false',
+ 'position_in_routes' => 'before',
+ 'position_in_class' => 'before',
+ 'position_in_test' => 'before',
+ 'position_in_fixture' => 'before',
+ 'position_in_factory' => 'before',
+ 'position_in_serializer' => 'before',
+ 'show_foreign_keys' => 'true',
+ 'show_indexes' => 'false',
+ 'simple_indexes' => 'false',
+ 'model_dir' => 'app/models',
+ 'root_dir' => '',
+ 'include_version' => 'false',
+ 'require' => '',
+ 'exclude_tests' => 'true',
+ 'exclude_fixtures' => 'true',
+ 'exclude_factories' => 'true',
+ 'exclude_serializers' => 'true',
+ 'exclude_scaffolds' => 'true',
+ 'exclude_controllers' => 'true',
+ 'exclude_helpers' => 'true',
+ 'ignore_model_sub_dir' => 'false',
+ 'ignore_columns' => nil,
+ 'ignore_unknown_models' => 'false',
+ 'hide_limit_column_types' => 'integer,boolean',
+ 'skip_on_db_migrate' => 'false',
+ 'format_bare' => 'true',
+ 'format_rdoc' => 'false',
+ 'format_markdown' => 'false',
+ 'sort' => 'false',
+ 'force' => 'false',
+ 'trace' => 'false',
+ 'wrapper_open' => nil,
+ 'wrapper_close' => nil,
+ )
+ end
+
+ Annotate.load_tasks
+end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4921c6e0bcf..1c706dc11b3 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -29,7 +29,10 @@ namespace :gitlab do
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
connection.execute('TRUNCATE schema_migrations')
- tables.each { |t| connection.execute("DROP TABLE #{t}") }
+ # Drop tables with cascade to avoid dependent table errors
+ # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
+ # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
+ tables.each { |t| connection.execute("DROP TABLE #{t} CASCADE") }
end
end
end
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
deleted file mode 100644
index d7a7ba1c5b6..00000000000
--- a/spec/controllers/admin/impersonation_controller_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ImpersonationController do
- let(:admin) { create(:admin) }
-
- before do
- sign_in(admin)
- end
-
- describe 'CREATE #impersonation when blocked' do
- let(:blocked_user) { create(:user, state: :blocked) }
-
- it 'does not allow impersonation' do
- post :create, id: blocked_user.username
-
- expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
- end
- end
-end
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
new file mode 100644
index 00000000000..eb82476b179
--- /dev/null
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationsController do
+ let(:impersonator) { create(:admin) }
+ let(:user) { create(:user) }
+
+ describe "DELETE destroy" do
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ delete :destroy
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when not impersonating" do
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when impersonating" do
+ before do
+ session[:impersonator_id] = impersonator.id
+ end
+
+ context "when the impersonator is not admin (anymore)" do
+ before do
+ impersonator.admin = false
+ impersonator.save
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is admin" do
+ context "when the impersonator is blocked" do
+ before do
+ impersonator.block!
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is not blocked" do
+ it "redirects to the impersonated user's page" do
+ delete :destroy
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it "signs us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(impersonator)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 9ef8ba1b097..ce2a62ae1fd 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
describe Admin::UsersController do
let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
before do
- sign_in(create(:admin))
+ sign_in(admin)
end
describe 'DELETE #user with projects' do
@@ -112,4 +113,50 @@ describe Admin::UsersController do
patch :disable_two_factor, id: user.to_param
end
end
+
+ describe "POST impersonate" do
+ context "when the user is blocked" do
+ before do
+ user.block!
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
+ end
+
+ it "doesn't sign us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(admin)
+ end
+ end
+
+ context "when the user is not blocked" do
+ it "stores the impersonator in the session" do
+ post :impersonate, id: user.username
+
+ expect(session[:impersonator_id]).to eq(admin.id)
+ end
+
+ it "signs us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(user)
+ end
+
+ it "redirects to root" do
+ post :impersonate, id: user.username
+
+ expect(response).to redirect_to(root_path)
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index d6e4cd71ce6..2b2ad3b9412 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -40,6 +40,45 @@ describe Projects::IssuesController do
end
end
+ describe 'PUT #update' do
+ context 'when moving issue to another private project' do
+ let(:another_project) { create(: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] }
+
+ it 'moves issue to another project' do
+ move_issue
+
+ expect(response).to have_http_status :found
+ expect(another_project.issues).to_not be_empty
+ end
+ end
+
+ context 'when user does not have access to move issue' do
+ it 'responds with 404' do
+ move_issue
+
+ expect(response).to have_http_status :not_found
+ end
+ end
+
+ def move_issue
+ put :update,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.iid,
+ issue: { title: 'New title' },
+ move_to_project_id: another_project.id
+ end
+ end
+ end
+
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 94dd935a039..3195fb3ddcc 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,5 +1,9 @@
FactoryGirl.define do
factory :project_hook do
url { FFaker::Internet.uri('http') }
+
+ trait :token do
+ token { SecureRandom.hex(10) }
+ end
end
end
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
new file mode 100644
index 00000000000..24e83d44010
--- /dev/null
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe 'Dashboard > label filter', feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+ let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) }
+ let(:label) { create(:label, title: 'bug', color: '#ff0000') }
+ let(:label2) { create(:label, title: 'bug') }
+
+ before do
+ project.labels << label
+ project2.labels << label2
+
+ login_as(user)
+ visit issues_dashboard_path
+ end
+
+ context 'duplicate labels' do
+ it 'should remove duplicate labels' do
+ page.within('.labels-filter') do
+ click_button 'Label'
+ end
+
+ page.within('.dropdown-menu-labels') do
+ expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1)
+ end
+ end
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index b57131f68d5..d5755c293c5 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -264,12 +264,14 @@ describe 'Issues', feature: true do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
expect(first_issue).to include('foo')
+ expect(last_issue).to include('baz')
end
it 'sorts by least recently due milestone' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
expect(first_issue).to include('bar')
+ expect(last_issue).to include('baz')
end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 4433ef2d6f1..8c38dd5b122 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -37,7 +37,7 @@ feature 'Login', feature: true do
end
def enter_code(code)
- fill_in 'Two-factor authentication code', with: code
+ fill_in 'Two-factor Authentication code', with: code
click_button 'Verify code'
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 00b60bd0e75..e296078bad8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
+
+ context 'when target project cannot be viewed by the current user' do
+ it 'does not leak the private project name & namespace' do
+ private_project = create(:project, :private)
+
+ visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+
+ expect(page).not_to have_content private_project.to_reference
+ end
+ end
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
new file mode 100644
index 00000000000..7e6eef65873
--- /dev/null
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User creates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
new file mode 100644
index 00000000000..ef82d2375dd
--- /dev/null
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User updates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+end
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 51b754ff85c..58aabd913eb 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -7,10 +7,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq users_almost_there_path
@@ -25,10 +25,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: existing_user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
@@ -42,10 +42,10 @@ feature 'Signup', feature: true do
visit root_path
- fill_in 'user_name', with: user.name
- fill_in 'user_username', with: user.username
- fill_in 'user_email', with: existing_user.email
- fill_in 'user_password_sign_up', with: user.password
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c1248162031..cf116040394 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -5,10 +5,10 @@ feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
- fill_in 'user_name', with: 'Name Surname'
- fill_in 'user_username', with: 'Great'
- fill_in 'user_email', with: 'name@mail.com'
- fill_in 'user_password_sign_up', with: 'password1234'
+ fill_in 'new_user_name', with: 'Name Surname'
+ fill_in 'new_user_username', with: 'Great'
+ fill_in 'new_user_email', with: 'name@mail.com'
+ fill_in 'new_user_password', with: 'password1234'
expect { click_button 'Sign up' }.to change { User.count }.by(1)
end
@@ -31,10 +31,10 @@ feature 'Users', feature: true do
scenario 'Should show one error if email is already taken' do
visit new_user_session_path
- fill_in 'user_name', with: 'Another user name'
- fill_in 'user_username', with: 'anotheruser'
- fill_in 'user_email', with: user.email
- fill_in 'user_password_sign_up', with: '12341234'
+ fill_in 'new_user_name', with: 'Another user name'
+ fill_in 'new_user_username', with: 'anotheruser'
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: '12341234'
expect { click_button 'Sign up' }.to change { User.count }.by(0)
expect(page).to have_text('Email has already been taken')
expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}'
diff --git a/spec/fixtures/sanitized.svg b/spec/fixtures/sanitized.svg
new file mode 100644
index 00000000000..8f84b8f5e20
--- /dev/null
+++ b/spec/fixtures/sanitized.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682">
+
+ <defs>
+ <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style>
+ </defs>
+ <title>stacked_wm</title>
+ <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/>
+ <g id="g12">
+ <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/>
+ </g>
+ <g id="g24">
+ <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/>
+ </g>
+ <g id="g28">
+ <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/>
+ </g>
+ <g id="g32">
+ <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/>
+ </g>
+ <g id="g36">
+ <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/>
+ </g>
+ <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/>
+ <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/>
+ <g id="g44">
+ <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/>
+ </g>
+ <g id="g48">
+ <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/>
+ </g>
+ <g id="g56">
+ <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/>
+ </g>
+ <g id="g64">
+ <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/>
+ </g>
+ <g id="g72">
+ <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/>
+ </g>
+ <g id="g76">
+ <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/>
+ </g>
+ <g id="g80">
+ <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/>
+ </g>
+ <g id="g84">
+ <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/>
+ </g>
+</svg>
diff --git a/spec/fixtures/unsanitized.svg b/spec/fixtures/unsanitized.svg
new file mode 100644
index 00000000000..3957557334b
--- /dev/null
+++ b/spec/fixtures/unsanitized.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682" filterMe="test">
+ <iframe src="http://www.google.com"></iframe>
+ <defs>
+ <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style>
+ </defs>
+ <title>stacked_wm</title>
+ <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/>
+ <g id="g12">
+ <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/>
+ </g>
+ <g id="g24">
+ <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/>
+ </g>
+ <g id="g28">
+ <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/>
+ </g>
+ <g id="g32">
+ <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/>
+ </g>
+ <g id="g36">
+ <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/>
+ </g>
+ <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/>
+ <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/>
+ <g id="g44">
+ <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/>
+ </g>
+ <g id="g48">
+ <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/>
+ </g>
+ <g id="g56">
+ <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/>
+ </g>
+ <g id="g64">
+ <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/>
+ </g>
+ <g id="g72">
+ <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/>
+ </g>
+ <g id="g76">
+ <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/>
+ </g>
+ <g id="g80">
+ <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/>
+ </g>
+ <g id="g84">
+ <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/>
+ </g>
+</svg>
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 87849230dbe..6d1c02db297 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -67,4 +67,16 @@ describe BlobHelper do
expect(result).to eq(expected)
end
end
+
+ describe "#sanitize_svg" do
+ let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') }
+ let(:data) { open(input_svg_path).read }
+ let(:expected_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') }
+ let(:expected) { open(expected_svg_path).read }
+
+ it 'should retain essential elements' do
+ blob = OpenStruct.new(data: data)
+ expect(sanitize_svg(blob).data).to eq(expected)
+ end
+ end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 543593cf389..bffe2c18b6f 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -30,6 +30,18 @@ describe IssuesHelper do
expect(url_for_project_issues).to eq ""
end
+ it 'returns an empty string if project_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project)).to eq ''
+ end
+
+ it 'returns an empty string if project_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -68,6 +80,18 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -105,6 +129,18 @@ describe IssuesHelper do
expect(url_for_new_issue).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 62389188d2c..29bcb8c5892 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -131,4 +131,13 @@ describe ProjectsHelper do
end
end
end
+
+ describe '#sanitized_import_error' do
+ it 'removes the repo path' do
+ repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git')
+ import_error = "Could not clone #{repo}\n"
+
+ expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
+ end
+ end
end
diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee
new file mode 100644
index 00000000000..c0bd8a29e43
--- /dev/null
+++ b/spec/javascripts/merge_request_widget_spec.js.coffee
@@ -0,0 +1,49 @@
+#= require merge_request_widget
+
+describe 'MergeRequestWidget', ->
+
+ beforeEach ->
+ window.notifyPermissions = () ->
+ window.notify = () ->
+ @opts = {
+ ci_status_url:"http://sampledomain.local/ci/getstatus",
+ ci_status:"",
+ ci_message: {
+ normal: "Build {{status}} for \"{{title}}\"",
+ preparing: "{{status}} build for \"{{title}}\""
+ },
+ ci_title: {
+ preparing: "{{status}} build",
+ normal: "Build {{status}}"
+ },
+ gitlab_icon:"gitlab_logo.png",
+ builds_path:"http://sampledomain.local/sampleBuildsPath"
+ }
+ @class = new MergeRequestWidget(@opts)
+ @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98}
+
+ describe 'getCIStatus', ->
+ beforeEach ->
+ spyOn(jQuery, 'getJSON').and.callFake (req, cb) =>
+ cb(@ciStatusData)
+
+ it 'should call showCIStatus even if a notification should not be displayed', ->
+ spy = spyOn(@class, 'showCIStatus').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
+
+ it 'should call showCIStatus when a notification should be displayed', ->
+ spy = spyOn(@class, 'showCIStatus').and.stub()
+ @class.getCIStatus(true)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
+
+ it 'should call showCICoverage when the coverage rate is set', ->
+ spy = spyOn(@class, 'showCICoverage').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage)
+
+ it 'should not call showCICoverage when the coverage rate is not set', ->
+ @ciStatusData.coverage = null
+ spy = spyOn(@class, 'showCICoverage').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).not.toHaveBeenCalled()
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index aa0699f2ebf..af839f42421 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -34,18 +34,32 @@ describe Gitlab::BitbucketImport::Client, lib: true do
it 'retrieves issues over a number of pages' do
stub_request(:get,
"https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=0").
- to_return(status: 200,
- body: first_sample_data.to_json,
- headers: {})
+ to_return(status: 200,
+ body: first_sample_data.to_json,
+ headers: {})
stub_request(:get,
"https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=50").
- to_return(status: 200,
- body: second_sample_data.to_json,
- headers: {})
+ to_return(status: 200,
+ body: second_sample_data.to_json,
+ headers: {})
issues = client.issues(project_id)
expect(issues.count).to eq(95)
end
end
+
+ context 'project import' do
+ it 'calls .from_project with no errors' do
+ project = create(:empty_project)
+ project.create_or_update_import_data(credentials:
+ { user: "git",
+ password: nil,
+ bb_session: { bitbucket_access_token: "test",
+ bitbucket_access_token_secret: "test" } })
+ project.import_url = "ssh://git@bitbucket.org/test/test.git"
+
+ expect { described_class.from_project(project) }.to_not raise_error
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index a324a82e69f..55e86d4ceac 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -2,23 +2,25 @@ require 'spec_helper'
describe Gitlab::GithubImport::CommentFormatter, lib: true do
let(:project) { create(:project) }
- let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
- let(:base_data) do
+ let(:base) do
{
body: "I'm having a problem with this.",
user: octocat,
+ commit_id: nil,
+ diff_hunk: nil,
created_at: created_at,
updated_at: updated_at
}
end
- subject(:comment) { described_class.new(project, raw_data)}
+ subject(:comment) { described_class.new(project, raw)}
describe '#attributes' do
context 'when do not reference a portion of the diff' do
- let(:raw_data) { OpenStruct.new(base_data) }
+ let(:raw) { double(base) }
it 'returns formatted attributes' do
expected = {
@@ -36,24 +38,23 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
end
context 'when on a portion of the diff' do
- let(:diff_data) do
+ let(:diff) do
{
body: 'Great stuff',
commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
- diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...',
- path: 'file1.txt',
- position: 1
+ diff_hunk: "@@ -1,5 +1,9 @@\n class User\n def name\n- 'John Doe'\n+ 'Jane Doe'",
+ path: 'file1.txt'
}
end
- let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) }
+ let(:raw) { double(base.merge(diff)) }
it 'returns formatted attributes' do
expected = {
project: project,
note: "*Created by: octocat*\n\nGreat stuff",
commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
- line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1',
+ line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_4_3',
author_id: project.creator_id,
created_at: created_at,
updated_at: updated_at
@@ -64,15 +65,10 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
end
context 'when author is a GitLab user' do
- let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+ let(:raw) { double(base.merge(user: octocat)) }
- it 'returns project#creator_id as author_id when is not a GitLab user' do
- expect(comment.attributes.fetch(:author_id)).to eq project.creator_id
- end
-
- it 'returns GitLab user id as author_id when is a GitLab user' do
+ it 'returns GitLab user id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
-
expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
end
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 04bc2dcfb16..37a27d73aab 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -43,51 +43,65 @@ describe WebHook, models: true do
end
describe "execute" do
+ let(:project) { create(:project) }
+ let(:project_hook) { create(:project_hook) }
+
before(:each) do
- @project_hook = create(:project_hook)
- @project = create(:project)
- @project.hooks << [@project_hook]
+ project.hooks << [project_hook]
@data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
- WebMock.stub_request(:post, @project_hook.url)
+ WebMock.stub_request(:post, project_hook.url)
+ end
+
+ context 'when token is defined' do
+ let(:project_hook) { create(:project_hook, :token) }
+
+ it 'POSTs to the webhook URL' do
+ project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: { 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => 'Push Hook',
+ 'X-Gitlab-Token' => project_hook.token }
+ ).once
+ end
end
it "POSTs to the webhook URL" do
- @project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, @project_hook.url).with(
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
+ project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
).once
end
it "POSTs the data as JSON" do
- @project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, @project_hook.url).with(
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
+ project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
).once
end
it "catches exceptions" do
expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
- expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
+ expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
end
it "handles SSL exceptions" do
expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
it "handles 200 status code" do
- WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success")
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
it "handles 2xx status codes" do
- WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success")
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
end
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 31b2c90122d..e771f35811e 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -27,86 +27,51 @@ describe BambooService, models: true do
end
describe 'Validations' do
- describe '#bamboo_url' do
- it 'does not validate the presence of bamboo_url if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
- end
-
- it 'validates the presence of bamboo_url if service is active' do
- bamboo_service = service
- bamboo_service.active = true
-
- expect(bamboo_service).to validate_presence_of(:bamboo_url)
- end
- end
+ subject { service }
- describe '#build_key' do
- it 'does not validate the presence of build_key if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(bamboo_service).not_to validate_presence_of(:build_key)
- end
+ it { is_expected.to validate_presence_of(:build_key) }
+ it { is_expected.to validate_presence_of(:bamboo_url) }
+ it_behaves_like 'issue tracker service URL attribute', :bamboo_url
- it 'validates the presence of build_key if service is active' do
- bamboo_service = service
- bamboo_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(bamboo_service).to validate_presence_of(:build_key)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(bamboo_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(bamboo_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(bamboo_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = nil
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(bamboo_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_key) }
+ it { is_expected.not_to validate_presence_of(:bamboo_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 88cd624877a..60364df2015 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -26,6 +26,23 @@ describe BuildkiteService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:token) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe 'commits methods' do
before do
@project = Project.new
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
index 7c23c2efccd..236df8f047d 100644
--- a/spec/models/project_services/builds_email_service_spec.rb
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -1,76 +1,71 @@
require 'spec_helper'
describe BuildsEmailService do
- let(:build) { create(:ci_build) }
- let(:data) { Gitlab::BuildDataBuilder.build(build) }
- let!(:project) { create(:project, :public, ci_id: 1) }
- let(:service) { described_class.new(project: project, active: true) }
+ let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) }
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+
+ context 'when pusher is added' do
+ before { subject.add_pusher = true }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
describe '#execute' do
it 'sends email' do
- service.recipients = 'test@gitlab.com'
+ subject.recipients = 'test@gitlab.com'
data[:build_status] = 'failed'
+
expect(BuildEmailWorker).to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with succeeded build and notify_only_broken_builds on' do
- expect(service).to receive(:notify_only_broken_builds).and_return(true)
+ expect(subject).to receive(:notify_only_broken_builds).and_return(true)
data[:build_status] = 'success'
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with failed build and build_allow_failure is true' do
data[:build_status] = 'failed'
data[:build_allow_failure] = true
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with unknown build status' do
data[:build_status] = 'foo'
- expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- it 'does not send email when recipients list is empty' do
- service.recipients = ' ,, '
- data[:build_status] = 'failed'
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- end
-
- describe 'validations' do
-
- context 'when pusher is not added' do
- before { service.add_pusher = false }
-
- it 'does not allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be false
- end
-
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
- context 'when pusher is added' do
- before { service.add_pusher = true }
+ it 'does not send email when recipients list is empty' do
+ subject.recipients = ' ,, '
+ data[:build_status] = 'failed'
- it 'does allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be true
- end
+ expect(BuildEmailWorker).not_to receive(:perform_async)
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
end
end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
new file mode 100644
index 00000000000..3e6da42803b
--- /dev/null
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CampfireService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
new file mode 100644
index 00000000000..ff976f8ec59
--- /dev/null
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CustomIssueTrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index a2cf68a9e38..3a8e67438fc 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,25 +28,18 @@ describe DroneCiService, models: true do
describe 'validations' do
context 'active' do
- before { allow(subject).to receive(:activated?).and_return(true) }
+ before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
- it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
- it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
- it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+ it_behaves_like 'issue tracker service URL attribute', :drone_url
end
context 'inactive' do
- before { allow(subject).to receive(:activated?).and_return(false) }
+ before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
- it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
end
end
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
new file mode 100644
index 00000000000..e6f78898c82
--- /dev/null
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe EmailsOnPushService do
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+end
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index d37978720bf..5fe5ea7d2df 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do
it { should have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:external_wiki_url) }
+ it_behaves_like 'issue tracker service URL attribute', :external_wiki_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
- it { should validate_presence_of :external_wiki_url }
+ it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index ff7fbcaa004..b7e627e6518 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -26,6 +26,20 @@ describe FlowdockService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index ecb3ccb1673..a08f1ac229f 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -26,6 +26,22 @@ describe GemnasiumService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:api_key) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 3518dbd1728..7a1f106d6e3 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ subject { described_class.new(project: create(:project), active: true) }
+
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ end
+
+ context 'when service is inactive' do
+ subject { described_class.new(project: create(:project), active: false) }
+
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
describe 'project and issue urls' do
let(:project) { create(:project) }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index d878162a220..6fb5cad5011 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -26,6 +26,20 @@ describe HipchatService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:hipchat) { HipchatService.new }
let(:user) { create(:user, username: 'username') }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b783b1a576e..4ee022a5171 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -29,14 +29,16 @@ describe IrkerService, models: true do
end
describe 'Validations' do
- before do
- subject.active = true
- subject.properties['recipients'] = _recipients
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
end
- context 'active' do
- let(:_recipients) { nil }
- it { should validate_presence_of :recipients }
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f8193170ae..5309cfb99ff 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -26,6 +26,30 @@ describe JiraService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :api_url
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -72,7 +96,7 @@ describe JiraService, models: true do
context "when a password was previously set" do
before do
- @jira_service = JiraService.create(
+ @jira_service = JiraService.create!(
project: create(:project),
properties: {
api_url: 'http://jira.example.com/rest/api/2',
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
new file mode 100644
index 00000000000..f37edd4d970
--- /dev/null
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe PivotaltrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 96039f9491b..555d9757b47 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -27,14 +27,20 @@ describe PushoverService, models: true do
end
describe 'Validations' do
- context 'active' do
- before do
- subject.active = true
- end
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :api_key }
- it { is_expected.to validate_presence_of :user_key }
- it { is_expected.to validate_presence_of :priority }
+ it { is_expected.to validate_presence_of(:api_key) }
+ it { is_expected.to validate_presence_of(:user_key) }
+ it { is_expected.to validate_presence_of(:priority) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ it { is_expected.not_to validate_presence_of(:user_key) }
+ it { is_expected.not_to validate_presence_of(:priority) }
end
end
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
new file mode 100644
index 00000000000..7d14f6e8280
--- /dev/null
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe RedmineService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 478d59be08b..a97b7560137 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -26,13 +26,18 @@ describe SlackService, models: true do
it { is_expected.to have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :webhook }
+ it { is_expected.to validate_presence_of(:webhook) }
+ it_behaves_like 'issue tracker service URL attribute', :webhook
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:webhook) }
end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index bc7423cee69..ad24b895170 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -27,86 +27,51 @@ describe TeamcityService, models: true do
end
describe 'Validations' do
- describe '#teamcity_url' do
- it 'does not validate the presence of teamcity_url if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
- end
-
- it 'validates the presence of teamcity_url if service is active' do
- teamcity_service = service
- teamcity_service.active = true
-
- expect(teamcity_service).to validate_presence_of(:teamcity_url)
- end
- end
+ subject { service }
- describe '#build_type' do
- it 'does not validate the presence of build_type if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(teamcity_service).not_to validate_presence_of(:build_type)
- end
+ it { is_expected.to validate_presence_of(:build_type) }
+ it { is_expected.to validate_presence_of(:teamcity_url) }
+ it_behaves_like 'issue tracker service URL attribute', :teamcity_url
- it 'validates the presence of build_type if service is active' do
- teamcity_service = service
- teamcity_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(teamcity_service).to validate_presence_of(:build_type)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(teamcity_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(teamcity_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(teamcity_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = nil
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(teamcity_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_type) }
+ it { is_expected.not_to validate_presence_of(:teamcity_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e33c7d62ff4..5b1cf71337e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -798,4 +798,18 @@ describe Project, models: true do
end
end
end
+
+ describe '#protected_branch?' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns true when a branch is a protected branch' do
+ project.protected_branches.create!(name: 'foo')
+
+ expect(project.protected_branch?('foo')).to eq(true)
+ end
+
+ it 'returns false when a branch is not a protected branch' do
+ expect(project.protected_branch?('foo')).to eq(false)
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 397bb5a8028..34a13f9b5c9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -561,7 +561,7 @@ describe Repository, models: true do
end
describe :skip_merged_commit do
- subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
+ subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
@@ -858,13 +858,30 @@ describe Repository, models: true do
end
describe '#add_tag' do
- it 'adds a tag' do
- expect(repository).to receive(:before_push_tag)
+ context 'with a valid target' do
+ let(:user) { build_stubbed(:user) }
- expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
- with(repository.path_with_namespace, '8.5', 'master', 'foo')
+ it 'creates the tag using rugged' do
+ expect(repository.rugged.tags).to receive(:create).
+ with('8.5', repository.commit('master').id,
+ hash_including(message: 'foo',
+ tagger: hash_including(name: user.name, email: user.email))).
+ and_call_original
- repository.add_tag('8.5', 'master', 'foo')
+ repository.add_tag(user, '8.5', 'master', 'foo')
+ end
+
+ it 'returns a Gitlab::Git::Tag object' do
+ tag = repository.add_tag(user, '8.5', 'master', 'foo')
+
+ expect(tag).to be_a(Gitlab::Git::Tag)
+ end
+ end
+
+ context 'with an invalid target' do
+ it 'returns false' do
+ expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false
+ end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e28998d51b5..cb82ca7802d 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -32,6 +32,41 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context "since optional parameter" do
+ it "should return project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ since = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
+
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
+ end
+
+ context "until optional parameter" do
+ it "should return project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+
+ expect(json_response.size).to eq(commits.size - 1)
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
+ end
+
+ context "invalid xmlschema date parameters" do
+ it "should return an invalid parameter error message" do
+ get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
+
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
+ end
+ end
end
describe "GET /projects:id/repository/commits/:sha" do
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 344f0fe0b7f..241995041bb 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -127,7 +127,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
- milestone.issues << create(:issue)
+ milestone.issues << create(:issue, project: project)
end
it 'should return project issues for a particular milestone' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
@@ -140,5 +140,34 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
expect(response.status).to eq(401)
end
+
+ describe 'confidential issues' do
+ let(:public_project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: public_project) }
+ let(:issue) { create(:issue, project: public_project) }
+ let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ before do
+ public_project.team << [user, :developer]
+ milestone.issues << issue << confidential_issue
+ end
+
+ it 'returns confidential issues to team members' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
+ end
+
+ it 'does not return confidential issues to regular users' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
+ end
+ end
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index ec9eda0a2ed..49091fc0f49 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let!(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
let!(:snippet) { create(:project_snippet, project: project, author: user) }
@@ -45,7 +45,7 @@ describe API::API, api: true do
end
it "should return a 404 error when issue id not found" do
- get api("/projects/#{project.id}/issues/123/notes", user)
+ get api("/projects/#{project.id}/issues/12345/notes", user)
expect(response.status).to eq(404)
end
@@ -106,7 +106,7 @@ describe API::API, api: true do
end
it "should return a 404 error if issue note not found" do
- get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -134,7 +134,7 @@ describe API::API, api: true do
end
it "should return a 404 error if snippet note not found" do
- get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
expect(response.status).to eq(404)
end
end
@@ -191,6 +191,27 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context 'when user does not have access to create noteable' do
+ let(:private_issue) { create(:issue, project: create(:project, :private)) }
+
+ ##
+ # We are posting to project user has access to, but we use issue id
+ # from a different project, see #15577
+ #
+ before do
+ post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
+ body: 'Hi!'
+ end
+
+ it 'responds with 500' do
+ expect(response.status).to eq 500
+ end
+
+ it 'does not create new note' do
+ expect(private_issue.notes.reload).to be_empty
+ end
+ end
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
@@ -211,7 +232,7 @@ describe API::API, api: true do
end
it 'should return a 404 error when note id not found' do
- put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+ put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
expect(response.status).to eq(404)
end
@@ -233,7 +254,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -248,7 +269,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -268,7 +289,7 @@ describe API::API, api: true do
end
it 'returns a 404 error when note id not found' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -288,7 +309,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user)
+ "notes/12345", user)
expect(response.status).to eq(404)
end
@@ -308,7 +329,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/merge_requests/"\
- "#{merge_request.id}/notes/123", user)
+ "#{merge_request.id}/notes/12345", user)
expect(response.status).to eq(404)
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 3722ddf5a33..9706d060cfa 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -15,4 +15,91 @@ describe API::API, api: true do
expect(json_response['expires_at']).to be_nil
end
end
+
+ describe 'GET /projects/:project_id/snippets/' do
+ it 'all snippets available to team member' do
+ project = create(:project, :public)
+ user = create(:user)
+ project.team << [user, :developer]
+ public_snippet = create(:project_snippet, :public, project: project)
+ internal_snippet = create(:project_snippet, :internal, project: project)
+ private_snippet = create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(3)
+ expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ end
+
+ it 'hides private snippets from regular user' do
+ project = create(:project, :public)
+ user = create(:user)
+ create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(0)
+ end
+ end
+
+ describe 'POST /projects/:project_id/snippets/' do
+ it 'creates a new snippet' do
+ admin = create(:admin)
+ project = create(:project)
+ params = {
+ title: 'Test Title',
+ file_name: 'test.rb',
+ code: 'puts "hello world"',
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ }
+
+ post api("/projects/#{project.id}/snippets/", admin), params
+
+ expect(response.status).to eq(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:code])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(params[:visibility_level])
+ end
+ end
+
+ describe 'PUT /projects/:project_id/snippets/:id/' do
+ it 'updates snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+ new_content = 'New content'
+
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+
+ expect(response.status).to eq(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ end
+ end
+
+ describe 'DELETE /projects/:project_id/snippets/:id/' do
+ it 'deletes snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe 'GET /projects/:project_id/snippets/:id/raw' do
+ it 'returns raw text' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
+
+ expect(response.status).to eq(200)
+ expect(response.content_type).to eq 'text/plain'
+ expect(response.body).to eq(snippet.content)
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fccd08bd6da..66193eac051 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -11,7 +11,7 @@ describe API::API, api: true do
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
- let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
+ let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index edcb2bedbf7..12e170b232f 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -147,7 +147,7 @@ describe API::API, api: true do
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Tag already exists')
+ expect(json_response['message']).to eq('Tag v8.0.0 already exists')
end
it 'should return 400 if ref name is invalid' do
@@ -155,7 +155,7 @@ describe API::API, api: true do
tag_name: 'mytag',
ref: 'foo'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Invalid reference name')
+ expect(json_response['message']).to eq('Target foo is invalid')
end
end
diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb
new file mode 100644
index 00000000000..91f9e663b66
--- /dev/null
+++ b/spec/services/create_tag_service_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe CreateTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ it 'creates the tag and returns success' do
+ response = service.execute('v42.42.42', 'master', 'Foo')
+
+ expect(response[:status]).to eq(:success)
+ expect(response[:tag]).to be_a Gitlab::Git::Tag
+ expect(response[:tag].name).to eq('v42.42.42')
+ end
+
+ context 'when target is invalid' do
+ it 'returns an error' do
+ response = service.execute('v1.1.0', 'foo', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Target foo is invalid')
+ end
+ end
+
+ context 'when tag already exists' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(Rugged::TagError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag v1.1.0 already exists')
+ end
+ end
+
+ context 'when pre-receive hook fails' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(GitHooksService::PreReceiveError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag creation was rejected by Git hook')
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 2a5e4ac3ec4..c15e26189a5 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -7,10 +7,11 @@ describe Issues::MoveService, services: true do
let(:description) { 'Some issue description' }
let(:old_project) { create(:project) }
let(:new_project) { create(:project) }
+ let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') }
let(:old_issue) do
create(:issue, title: title, description: description,
- project: old_project, author: author)
+ project: old_project, author: author, milestone: milestone1)
end
let(:move_service) do
@@ -21,11 +22,24 @@ describe Issues::MoveService, services: true do
before do
old_project.team << [user, :reporter]
new_project.team << [user, :reporter]
+
+ ['label1', 'label2'].each do |label|
+ old_issue.labels << create(:label,
+ project_id: old_project.id,
+ title: label)
+ end
+
+ new_project.labels << create(:label, title: 'label1')
+ new_project.labels << create(:label, title: 'label2')
end
end
describe '#execute' do
shared_context 'issue move executed' do
+ let!(:milestone2) do
+ create(:milestone, project_id: new_project.id, title: 'v9.0')
+ end
+
let!(:new_issue) { move_service.execute(old_issue, new_project) }
end
@@ -39,6 +53,23 @@ describe Issues::MoveService, services: true do
expect(new_issue.project).to eq new_project
end
+ it 'assigns milestone to new issue' do
+ expect(new_issue.reload.milestone.title).to eq 'v9.0'
+ expect(new_issue.reload.milestone).to eq(milestone2)
+ end
+
+ it 'assign labels to new issue' do
+ expected_label_titles = new_issue.reload.labels.map(&:title)
+ expect(expected_label_titles).to include 'label1'
+ expect(expected_label_titles).to include 'label2'
+ expect(expected_label_titles.size).to eq 2
+
+ new_issue.labels.each do |label|
+ expect(new_project.labels).to include(label)
+ expect(old_project.labels).not_to include(label)
+ end
+ end
+
it 'rewrites issue title' do
expect(new_issue.title).to eq title
end
@@ -72,11 +103,6 @@ describe Issues::MoveService, services: true do
expect(new_issue.author).to eq author
end
- it 'removes data that is invalid in new context' do
- expect(new_issue.milestone).to be_nil
- expect(new_issue.labels).to be_empty
- end
-
it 'creates a new internal id for issue' do
expect(new_issue.iid).to be 1
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
new file mode 100644
index 00000000000..782d74ec5ec
--- /dev/null
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -0,0 +1,181 @@
+require 'spec_helper'
+
+describe MergeRequests::BuildService, services: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:issue_confidential) { false }
+ let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) }
+ let(:description) { nil }
+ let(:source_branch) { 'feature-branch' }
+ let(:target_branch) { 'master' }
+ let(:merge_request) { service.execute }
+ let(:compare) { double(:compare, commits: commits) }
+ let(:commit_1) { double(:commit_1, safe_message: "Initial commit\n\nCreate the app") }
+ let(:commit_2) { double(:commit_2, safe_message: 'This is a bad commit message!') }
+ let(:commits) { nil }
+
+ let(:service) do
+ MergeRequests::BuildService.new(project, user,
+ description: description,
+ source_branch: source_branch,
+ target_branch: target_branch)
+ end
+
+ before do
+ allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
+ end
+
+ describe 'execute' do
+ context 'missing source branch' do
+ let(:source_branch) { '' }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('You must select source and target branch')
+ end
+ end
+
+ context 'missing target branch' do
+ let(:target_branch) { '' }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('You must select source and target branch')
+ end
+ end
+
+ context 'no commits in the diff' do
+ let(:commits) { [] }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+ end
+
+ context 'one commit in the diff' do
+ let(:commits) { [commit_1] }
+
+ it 'allows the merge request to be created' do
+ expect(merge_request.can_be_created).to eq(true)
+ end
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
+ end
+
+ it 'uses the description of the commit as the description of the merge request' do
+ expect(merge_request.description).to eq(commit_1.safe_message.split(/\n+/, 2).last)
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'keeps the description from the initial params' do
+ expect(merge_request.description).to eq(description)
+ end
+ end
+
+ context 'commit has no description' do
+ let(:commits) { [commit_2] }
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_2.safe_message)
+ end
+
+ it 'sets the description to nil' do
+ expect(merge_request.description).to be_nil
+ end
+ end
+
+ context 'branch starts with issue IID followed by a hyphen' do
+ let(:source_branch) { "#{issue.iid}-fix-issue" }
+
+ it 'appends "Closes #$issue-iid" to the description' do
+ expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\nCloses ##{issue.iid}")
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'appends "Closes #$issue-iid" to the description' do
+ expect(merge_request.description).to eq("#{description}\nCloses ##{issue.iid}")
+ end
+ end
+
+ context 'commit has no description' do
+ let(:commits) { [commit_2] }
+
+ it 'sets the description to "Closes #$issue-iid"' do
+ expect(merge_request.description).to eq("Closes ##{issue.iid}")
+ end
+ end
+ end
+ end
+
+ context 'more than one commit in the diff' do
+ let(:commits) { [commit_1, commit_2] }
+
+ it 'allows the merge request to be created' do
+ expect(merge_request.can_be_created).to eq(true)
+ end
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq('Feature branch')
+ end
+
+ it 'does not add a description' do
+ expect(merge_request.description).to be_nil
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'keeps the description from the initial params' do
+ expect(merge_request.description).to eq(description)
+ end
+ end
+
+ context 'branch starts with GitLab issue IID followed by a hyphen' do
+ let(:source_branch) { "#{issue.iid}-fix-issue" }
+
+ it 'sets the title to: Resolves "$issue-title"' do
+ expect(merge_request.title).to eq("Resolve \"#{issue.title}\"")
+ end
+
+ context 'issue does not exist' do
+ let(:source_branch) { "#{issue.iid.succ}-fix-issue" }
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq("#{issue.iid.succ} fix issue")
+ end
+ end
+
+ context 'issue is confidential' do
+ let(:issue_confidential) { true }
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq("#{issue.iid} fix issue")
+ end
+ end
+ end
+
+ context 'branch starts with external issue IID followed by a hyphen' do
+ let(:source_branch) { '12345-fix-issue' }
+
+ before { allow(project).to receive(:default_issues_tracker?).and_return(false) }
+
+ it 'sets the title to: Resolves External Issue $issue-iid' do
+ expect(merge_request.title).to eq('Resolve External Issue 12345')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index d7c72dc0811..4bbc4ddc3ab 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -10,7 +10,7 @@ describe NotificationService, services: true do
end
describe 'Keys' do
- describe :new_key do
+ describe '#new_key' do
let!(:key) { create(:personal_key) }
it { expect(notification.new_key(key)).to be_truthy }
@@ -22,7 +22,7 @@ describe NotificationService, services: true do
end
describe 'Email' do
- describe :new_email do
+ describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
@@ -147,8 +147,8 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_note do
- it do
+ describe '#new_note' do
+ it 'notifies the team members' do
notification.new_note(note)
# Notify all team members
@@ -177,6 +177,39 @@ describe NotificationService, services: true do
end
end
+ context 'project snippet note' do
+ let(:project) { create(:empty_project, :public) }
+ let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
+ let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') }
+
+ before do
+ build_team(note.project)
+ note.project.team << [note.author, :master]
+ ActionMailer::Base.deliveries.clear
+ end
+
+ describe '#new_note' do
+ it 'notifies the team members' do
+ notification.new_note(note)
+
+ # Notify all team members
+ note.project.team.members.each do |member|
+ # User with disabled notification should not be notified
+ next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
+ should_email(member)
+ end
+
+ should_email(note.noteable.author)
+ should_not_email(note.author)
+ should_email(@u_mentioned)
+ should_not_email(@u_disabled)
+ should_email(@u_not_mentioned)
+ end
+ end
+ end
+
context 'commit note' do
let(:project) { create(:project, :public) }
let(:note) { create(:note_on_commit, project: project) }
@@ -187,7 +220,7 @@ describe NotificationService, services: true do
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
end
- describe :new_note, :perform_enqueued_jobs do
+ describe '#new_note, #perform_enqueued_jobs' do
it do
notification.new_note(note)
@@ -230,7 +263,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_issue do
+ describe '#new_issue' do
it do
notification.new_issue(issue, @u_disabled)
@@ -289,7 +322,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_issue do
+ describe '#reassigned_issue' do
it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
@@ -419,7 +452,7 @@ describe NotificationService, services: true do
end
end
- describe :close_issue do
+ describe '#close_issue' do
it 'should sent email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
@@ -435,7 +468,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_issue do
+ describe '#reopen_issue' do
it 'should send email to issue assignee and issue author' do
notification.reopen_issue(issue, @u_disabled)
@@ -461,7 +494,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_merge_request do
+ describe '#new_merge_request' do
it do
notification.new_merge_request(merge_request, @u_disabled)
@@ -483,7 +516,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_merge_request do
+ describe '#reassigned_merge_request' do
it do
notification.reassigned_merge_request(merge_request, merge_request.author)
@@ -498,7 +531,7 @@ describe NotificationService, services: true do
end
end
- describe :relabel_merge_request do
+ describe '#relabel_merge_request' do
let(:label) { create(:label, merge_requests: [merge_request]) }
let(:label2) { create(:label) }
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
@@ -527,7 +560,7 @@ describe NotificationService, services: true do
end
end
- describe :closed_merge_request do
+ describe '#closed_merge_request' do
it do
notification.close_mr(merge_request, @u_disabled)
@@ -542,7 +575,7 @@ describe NotificationService, services: true do
end
end
- describe :merged_merge_request do
+ describe '#merged_merge_request' do
it do
notification.merge_mr(merge_request, @u_disabled)
@@ -557,7 +590,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_merge_request do
+ describe '#reopen_merge_request' do
it do
notification.reopen_mr(merge_request, @u_disabled)
@@ -581,7 +614,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :project_was_moved do
+ describe '#project_was_moved' do
it do
notification.project_was_moved(project, "gitlab/gitlab")
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 596d607f2a1..576d16e7ea3 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -51,10 +51,4 @@ FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
-# Work around a Rails 4.2.5.1 issue
-# See https://github.com/rspec/rspec-rails/issues/1532
-RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
- alias_method :find_all_anywhere, :find_all
-end
-
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
new file mode 100644
index 00000000000..b6d7436c360
--- /dev/null
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -0,0 +1,7 @@
+RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
+ it { is_expected.to allow_value('https://example.com').for(url_attr) }
+
+ it { is_expected.not_to allow_value('example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) }
+end
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 087e4c667d8..5a03bb77ebd 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -12,7 +12,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
- destroy_wiki(project)
+ break_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(true)
@@ -20,15 +20,38 @@ describe RepositoryCheck::SingleRepositoryWorker do
it 'skips wikis when disabled' do
project = create(:project_empty_repo, wiki_enabled: false)
- # Make sure the test would fail if it checked the wiki repo
- destroy_wiki(project)
+ # Make sure the test would fail if the wiki repo was checked
+ break_wiki(project)
subject.perform(project.id)
expect(project.reload.last_repository_check_failed).to eq(false)
end
- def destroy_wiki(project)
- FileUtils.rm_rf(project.wiki.repository.path_to_repo)
+ it 'creates missing wikis' do
+ project = create(:project_empty_repo, wiki_enabled: true)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(project.reload.last_repository_check_failed).to eq(false)
+ end
+
+ it 'does not create a wiki if the main repo does not exist at all' do
+ project = create(:project_empty_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(File.exist?(wiki_path(project))).to eq(false)
+ end
+
+ def break_wiki(project)
+ FileUtils.rm_rf(wiki_path(project) + '/objects')
+ end
+
+ def wiki_path(project)
+ project.wiki.repository.path_to_repo
end
end