summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Lopez <james@jameslopez.es>2016-06-03 11:17:46 +0200
committerJames Lopez <james@jameslopez.es>2016-06-03 11:17:46 +0200
commit398f0071a6b77a7e26e612b38105ae4bd702ef22 (patch)
treeb64afe0bc7753cebd66e899ae7ecb830ff2f55ea
parent721014c92799219d357b1b7c971d4c0b6050ff2a (diff)
parent3f4ac2ff60c9d83ec65b19070e4d054e12e67dd2 (diff)
downloadgitlab-ce-398f0071a6b77a7e26e612b38105ae4bd702ef22.tar.gz
merge hell
-rw-r--r--.gitignore78
-rw-r--r--.rubocop.yml273
-rw-r--r--.vagrant_enabled (renamed from app/views/projects/notes/_commit_discussion.html.haml)0
-rw-r--r--CHANGELOG154
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile39
-rw-r--r--Gemfile.lock212
-rw-r--r--README.md6
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/ci/arch.jpgbin25222 -> 0 bytes
-rw-r--r--app/assets/images/ci/favicon.icobin5430 -> 0 bytes
-rw-r--r--app/assets/images/ci/loader.gifbin4405 -> 0 bytes
-rw-r--r--app/assets/images/ci/no_avatar.pngbin1337 -> 0 bytes
-rw-r--r--app/assets/images/ci/rails.pngbin6646 -> 0 bytes
-rw-r--r--app/assets/images/ci/service_sample.pngbin76024 -> 0 bytes
-rw-r--r--app/assets/images/mailers/gitlab_header_logo.pngbin0 -> 7096 bytes
-rw-r--r--app/assets/images/mailers/gitlab_tanuki_2x.pngbin0 -> 2545 bytes
-rw-r--r--app/assets/javascripts/api.js.coffee35
-rw-r--r--app/assets/javascripts/application.js.coffee43
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selector.js.coffee58
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee1
-rw-r--r--app/assets/javascripts/calendar.js.coffee34
-rw-r--r--app/assets/javascripts/ci/application.js.coffee28
-rw-r--r--app/assets/javascripts/ci/build.coffee24
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee3
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee35
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee19
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee82
-rw-r--r--app/assets/javascripts/graphs/application.js.coffee7
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js.coffee (renamed from app/assets/javascripts/stat_graph.js.coffee)0
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js.coffee (renamed from app/assets/javascripts/stat_graph_contributors.js.coffee)1
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee (renamed from app/assets/javascripts/stat_graph_contributors_graph.js.coffee)2
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee (renamed from app/assets/javascripts/stat_graph_contributors_util.js.coffee)0
-rw-r--r--app/assets/javascripts/issuable.js.coffee48
-rw-r--r--app/assets/javascripts/issuable_form.js.coffee26
-rw-r--r--app/assets/javascripts/issues.js.coffee38
-rw-r--r--app/assets/javascripts/layout_nav.js.coffee14
-rw-r--r--app/assets/javascripts/lib/type_utility.js.coffee9
-rw-r--r--app/assets/javascripts/lib/url_utility.js.coffee11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee3
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee15
-rw-r--r--app/assets/javascripts/notes.js.coffee12
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee36
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee22
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee8
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee4
-rw-r--r--app/assets/javascripts/sidebar.js.coffee2
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee9
-rw-r--r--app/assets/javascripts/users/application.js.coffee8
-rw-r--r--app/assets/javascripts/users/calendar.js.coffee198
-rw-r--r--app/assets/javascripts/users_select.js.coffee12
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/animations.scss72
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss11
-rw-r--r--app/assets/stylesheets/framework/buttons.scss18
-rw-r--r--app/assets/stylesheets/framework/calendar.scss72
-rw-r--r--app/assets/stylesheets/framework/common.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/files.scss31
-rw-r--r--app/assets/stylesheets/framework/forms.scss8
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss17
-rw-r--r--app/assets/stylesheets/framework/header.scss58
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss122
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss52
-rw-r--r--app/assets/stylesheets/framework/typography.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss15
-rw-r--r--app/assets/stylesheets/framework/zen.scss2
-rw-r--r--app/assets/stylesheets/mailers/devise.scss134
-rw-r--r--app/assets/stylesheets/mailers/repository_push_email.scss43
-rw-r--r--app/assets/stylesheets/pages/commit.scss24
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss2
-rw-r--r--app/assets/stylesheets/pages/editor.scss21
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/issues.scss10
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss31
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss24
-rw-r--r--app/assets/stylesheets/pages/profile.scss16
-rw-r--r--app/assets/stylesheets/pages/projects.scss170
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/assets/stylesheets/pages/settings.scss8
-rw-r--r--app/assets/stylesheets/pages/snippets.scss44
-rw-r--r--app/assets/stylesheets/pages/todos.scss14
-rw-r--r--app/assets/stylesheets/pages/tree.scss13
-rw-r--r--app/controllers/admin/abuse_reports_controller.rb2
-rw-r--r--app/controllers/admin/application_settings_controller.rb17
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb2
-rw-r--r--app/controllers/admin/health_check_controller.rb5
-rw-r--r--app/controllers/admin/keys_controller.rb2
-rw-r--r--app/controllers/admin/runners_controller.rb37
-rw-r--r--app/controllers/admin/spam_logs_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb3
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/autocomplete_controller.rb18
-rw-r--r--app/controllers/concerns/creates_commit.rb2
-rw-r--r--app/controllers/concerns/toggle_subscription_action.rb2
-rw-r--r--app/controllers/dashboard/labels_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/health_check_controller.rb22
-rw-r--r--app/controllers/jwt_controller.rb87
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/profiles/emails_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb8
-rw-r--r--app/controllers/projects/commit_controller.rb12
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/container_registry_controller.rb34
-rw-r--r--app/controllers/projects/find_file_controller.rb52
-rw-r--r--app/controllers/projects/hooks_controller.rb9
-rw-r--r--app/controllers/projects/imports_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb16
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb10
-rw-r--r--app/controllers/projects/pipelines_controller.rb59
-rw-r--r--app/controllers/projects/project_members_controller.rb4
-rw-r--r--app/controllers/projects/protected_branches_controller.rb2
-rw-r--r--app/controllers/projects/runners_controller.rb4
-rw-r--r--app/controllers/projects/variables_controller.rb30
-rw-r--r--app/controllers/projects/wikis_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb16
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb24
-rw-r--r--app/finders/group_projects_finder.rb2
-rw-r--r--app/finders/issuable_finder.rb10
-rw-r--r--app/finders/notes_finder.rb2
-rw-r--r--app/finders/pipelines_finder.rb38
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/helpers/application_helper.rb20
-rw-r--r--app/helpers/application_settings_helper.rb14
-rw-r--r--app/helpers/auth_helper.rb10
-rw-r--r--app/helpers/blob_helper.rb10
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb29
-rw-r--r--app/helpers/commits_helper.rb19
-rw-r--r--app/helpers/diff_helper.rb30
-rw-r--r--app/helpers/emails_helper.rb6
-rw-r--r--app/helpers/events_helper.rb92
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb10
-rw-r--r--app/helpers/issues_helper.rb21
-rw-r--r--app/helpers/javascript_helper.rb7
-rw-r--r--app/helpers/nav_helper.rb8
-rw-r--r--app/helpers/notes_helper.rb29
-rw-r--r--app/helpers/projects_helper.rb21
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/selects_helper.rb2
-rw-r--r--app/helpers/tab_helper.rb6
-rw-r--r--app/helpers/todos_helper.rb28
-rw-r--r--app/mailers/devise_mailer.rb2
-rw-r--r--app/mailers/emails/projects.rb8
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/models/ability.rb54
-rw-r--r--app/models/abuse_report.rb12
-rw-r--r--app/models/appearance.rb13
-rw-r--r--app/models/application_setting.rb80
-rw-r--r--app/models/audit_event.rb14
-rw-r--r--app/models/broadcast_message.rb14
-rw-r--r--app/models/ci/build.rb57
-rw-r--r--app/models/ci/commit.rb50
-rw-r--r--app/models/ci/runner.rb39
-rw-r--r--app/models/ci/runner_project.rb12
-rw-r--r--app/models/ci/trigger.rb13
-rw-r--r--app/models/ci/trigger_request.rb12
-rw-r--r--app/models/ci/variable.rb19
-rw-r--r--app/models/commit.rb23
-rw-r--r--app/models/commit_range.rb2
-rw-r--r--app/models/commit_status.rb54
-rw-r--r--app/models/concerns/issuable.rb57
-rw-r--r--app/models/concerns/mentionable.rb19
-rw-r--r--app/models/concerns/participable.rb94
-rw-r--r--app/models/concerns/subscribable.rb6
-rw-r--r--app/models/deploy_key.rb15
-rw-r--r--app/models/deploy_keys_project.rb11
-rw-r--r--app/models/email.rb11
-rw-r--r--app/models/event.rb51
-rw-r--r--app/models/forked_project_link.rb11
-rw-r--r--app/models/generic_commit_status.rb37
-rw-r--r--app/models/group.rb17
-rw-r--r--app/models/hooks/project_hook.rb22
-rw-r--r--app/models/hooks/service_hook.rb22
-rw-r--r--app/models/hooks/system_hook.rb22
-rw-r--r--app/models/hooks/web_hook.rb24
-rw-r--r--app/models/identity.rb12
-rw-r--r--app/models/issue.rb47
-rw-r--r--app/models/key.rb17
-rw-r--r--app/models/label.rb18
-rw-r--r--app/models/label_link.rb12
-rw-r--r--app/models/legacy_diff_note.rb157
-rw-r--r--app/models/lfs_object.rb12
-rw-r--r--app/models/lfs_objects_project.rb11
-rw-r--r--app/models/member.rb19
-rw-r--r--app/models/members/group_member.rb19
-rw-r--r--app/models/members/project_member.rb26
-rw-r--r--app/models/merge_request.rb59
-rw-r--r--app/models/merge_request_diff.rb45
-rw-r--r--app/models/milestone.rb91
-rw-r--r--app/models/namespace.rb25
-rw-r--r--app/models/network/graph.rb35
-rw-r--r--app/models/note.rb269
-rw-r--r--app/models/notification_setting.rb13
-rw-r--r--app/models/oauth_access_token.rb15
-rw-r--r--app/models/personal_snippet.rb16
-rw-r--r--app/models/project.rb161
-rw-r--r--app/models/project_group_link.rb12
-rw-r--r--app/models/project_import_data.rb15
-rw-r--r--app/models/project_services/asana_service.rb24
-rw-r--r--app/models/project_services/assembla_service.rb24
-rw-r--r--app/models/project_services/bamboo_service.rb24
-rw-r--r--app/models/project_services/buildkite_service.rb24
-rw-r--r--app/models/project_services/builds_email_service.rb24
-rw-r--r--app/models/project_services/campfire_service.rb24
-rw-r--r--app/models/project_services/ci_service.rb24
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb24
-rw-r--r--app/models/project_services/drone_ci_service.rb24
-rw-r--r--app/models/project_services/emails_on_push_service.rb24
-rw-r--r--app/models/project_services/external_wiki_service.rb26
-rw-r--r--app/models/project_services/flowdock_service.rb24
-rw-r--r--app/models/project_services/gemnasium_service.rb24
-rw-r--r--app/models/project_services/gitlab_ci_service.rb24
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb24
-rw-r--r--app/models/project_services/hipchat_service.rb24
-rw-r--r--app/models/project_services/irker_service.rb26
-rw-r--r--app/models/project_services/issue_tracker_service.rb24
-rw-r--r--app/models/project_services/jira_service.rb24
-rw-r--r--app/models/project_services/pivotaltracker_service.rb24
-rw-r--r--app/models/project_services/pushover_service.rb24
-rw-r--r--app/models/project_services/redmine_service.rb24
-rw-r--r--app/models/project_services/slack_service.rb24
-rw-r--r--app/models/project_services/slack_service/build_message.rb4
-rw-r--r--app/models/project_services/slack_service/issue_message.rb19
-rw-r--r--app/models/project_services/teamcity_service.rb24
-rw-r--r--app/models/project_snippet.rb19
-rw-r--r--app/models/project_wiki.rb18
-rw-r--r--app/models/protected_branch.rb12
-rw-r--r--app/models/release.rb12
-rw-r--r--app/models/repository.rb56
-rw-r--r--app/models/security_event.rb14
-rw-r--r--app/models/sent_notification.rb14
-rw-r--r--app/models/service.rb24
-rw-r--r--app/models/snippet.rb23
-rw-r--r--app/models/spam_log.rb17
-rw-r--r--app/models/subscription.rb13
-rw-r--r--app/models/todo.rb27
-rw-r--r--app/models/user.rb110
-rw-r--r--app/models/users_star_project.rb11
-rw-r--r--app/services/auth/container_registry_authentication_service.rb87
-rw-r--r--app/services/ci/create_pipeline_service.rb50
-rw-r--r--app/services/create_commit_builds_service.rb17
-rw-r--r--app/services/git_push_service.rb17
-rw-r--r--app/services/git_tag_push_service.rb2
-rw-r--r--app/services/issues/update_service.rb10
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb17
-rw-r--r--app/services/merge_requests/base_service.rb25
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb7
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb23
-rw-r--r--app/services/merge_requests/refresh_service.rb7
-rw-r--r--app/services/merge_requests/update_service.rb2
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/projects/autocomplete_service.rb4
-rw-r--r--app/services/projects/create_service.rb43
-rw-r--r--app/services/projects/destroy_service.rb12
-rw-r--r--app/services/projects/fork_service.rb14
-rw-r--r--app/services/projects/housekeeping_service.rb2
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb5
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/system_note_service.rb25
-rw-r--r--app/services/todo_service.rb30
-rw-r--r--app/services/wiki_pages/base_service.rb5
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml2
-rw-r--r--app/views/admin/application_settings/_form.html.haml21
-rw-r--r--app/views/admin/builds/index.html.haml3
-rw-r--r--app/views/admin/health_check/show.html.haml49
-rw-r--r--app/views/admin/runners/_runner.html.haml12
-rw-r--r--app/views/admin/runners/show.html.haml24
-rw-r--r--app/views/dashboard/issues.atom.builder5
-rw-r--r--app/views/dashboard/projects/index.atom.builder4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml14
-rw-r--r--app/views/devise/mailer/confirmation_instructions.html.erb9
-rw-r--r--app/views/devise/mailer/confirmation_instructions.html.haml16
-rw-r--r--app/views/devise/mailer/confirmation_instructions.text.erb9
-rw-r--r--app/views/devise/sessions/new.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml1
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event.atom.builder20
-rw-r--r--app/views/events/_event_issue.atom.haml2
-rw-r--r--app/views/events/_event_merge_request.atom.haml2
-rw-r--r--app/views/events/_event_note.atom.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/events/event/_common.html.haml2
-rw-r--r--app/views/events/event/_push.html.haml2
-rw-r--r--app/views/groups/_activities.html.haml2
-rw-r--r--app/views/groups/issues.atom.builder13
-rw-r--r--app/views/groups/show.atom.builder4
-rw-r--r--app/views/import/gitlab/status.html.haml2
-rw-r--r--app/views/issues/_issue.atom.builder14
-rw-r--r--app/views/kaminari/gitlab/_first_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_gap.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_last_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_next_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_paginator.html.haml6
-rw-r--r--app/views/kaminari/gitlab/_prev_page.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml3
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/_search.html.haml7
-rw-r--r--app/views/layouts/devise_mailer.html.haml34
-rw-r--r--app/views/layouts/header/_default.html.haml10
-rw-r--r--app/views/layouts/nav/_admin.html.haml5
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml14
-rw-r--r--app/views/layouts/nav/_group.html.haml75
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml2
-rw-r--r--app/views/layouts/nav/_profile.html.haml4
-rw-r--r--app/views/layouts/nav/_project.html.haml250
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml102
-rw-r--r--app/views/layouts/notify.html.haml1
-rw-r--r--app/views/layouts/project.html.haml2
-rw-r--r--app/views/layouts/project_settings.html.haml3
-rw-r--r--app/views/notify/_note_message.html.haml2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/notify/note_merge_request_email.html.haml4
-rw-r--r--app/views/notify/repository_push_email.html.haml59
-rw-r--r--app/views/notify/repository_push_email.text.haml38
-rw-r--r--app/views/profiles/accounts/show.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml2
-rw-r--r--app/views/projects/_builds_settings.html.haml119
-rw-r--r--app/views/projects/_home_panel.html.haml86
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_zen.html.haml2
-rw-r--r--app/views/projects/activity.html.haml1
-rw-r--r--app/views/projects/artifacts/browse.html.haml1
-rw-r--r--app/views/projects/badges/index.html.haml1
-rw-r--r--app/views/projects/blame/show.html.haml1
-rw-r--r--app/views/projects/blob/_editor.html.haml3
-rw-r--r--app/views/projects/blob/_header_title.html.haml1
-rw-r--r--app/views/projects/blob/edit.html.haml1
-rw-r--r--app/views/projects/blob/new.html.haml1
-rw-r--r--app/views/projects/blob/show.html.haml1
-rw-r--r--app/views/projects/branches/destroy.js.haml1
-rw-r--r--app/views/projects/branches/index.html.haml1
-rw-r--r--app/views/projects/branches/new.html.haml1
-rw-r--r--app/views/projects/builds/_header_title.html.haml1
-rw-r--r--app/views/projects/builds/index.html.haml5
-rw-r--r--app/views/projects/builds/show.html.haml8
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml5
-rw-r--r--app/views/projects/buttons/_notifications.html.haml6
-rw-r--r--app/views/projects/ci/builds/_build.html.haml10
-rw-r--r--app/views/projects/ci/commits/_commit.html.haml71
-rw-r--r--app/views/projects/commit/_builds.html.haml2
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml63
-rw-r--r--app/views/projects/commit/_ci_stage.html.haml15
-rw-r--r--app/views/projects/commit/_commit_box.html.haml78
-rw-r--r--app/views/projects/commit/builds.html.haml4
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.atom.builder14
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/commits/_head.html.haml8
-rw-r--r--app/views/projects/commits/_header_title.html.haml1
-rw-r--r--app/views/projects/commits/show.atom.builder15
-rw-r--r--app/views/projects/commits/show.html.haml1
-rw-r--r--app/views/projects/compare/index.html.haml1
-rw-r--r--app/views/projects/compare/show.html.haml1
-rw-r--r--app/views/projects/container_registry/_tag.html.haml21
-rw-r--r--app/views/projects/container_registry/index.html.haml39
-rw-r--r--app/views/projects/deploy_keys/index.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml8
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml14
-rw-r--r--app/views/projects/diffs/_text_file.html.haml9
-rw-r--r--app/views/projects/edit.html.haml445
-rw-r--r--app/views/projects/empty.html.haml10
-rw-r--r--app/views/projects/find_file/show.html.haml1
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml15
-rw-r--r--app/views/projects/graphs/_head.html.haml1
-rw-r--r--app/views/projects/graphs/_header_title.html.haml1
-rw-r--r--app/views/projects/graphs/ci.html.haml1
-rw-r--r--app/views/projects/graphs/commits.html.haml1
-rw-r--r--app/views/projects/graphs/languages.html.haml1
-rw-r--r--app/views/projects/graphs/show.html.haml3
-rw-r--r--app/views/projects/hooks/_project_hook.html.haml2
-rw-r--r--app/views/projects/hooks/index.html.haml9
-rw-r--r--app/views/projects/issues/_header_title.html.haml1
-rw-r--r--app/views/projects/issues/_issue.html.haml16
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml7
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/edit.html.haml1
-rw-r--r--app/views/projects/issues/index.atom.builder4
-rw-r--r--app/views/projects/issues/index.html.haml1
-rw-r--r--app/views/projects/issues/new.html.haml1
-rw-r--r--app/views/projects/issues/show.html.haml5
-rw-r--r--app/views/projects/labels/_header_title.html.haml1
-rw-r--r--app/views/projects/labels/edit.html.haml1
-rw-r--r--app/views/projects/labels/index.html.haml3
-rw-r--r--app/views/projects/labels/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/_header_title.html.haml1
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml18
-rw-r--r--app/views/projects/merge_requests/_show.html.haml1
-rw-r--r--app/views/projects/merge_requests/dropdowns/_branch.html.haml2
-rw-r--r--app/views/projects/merge_requests/edit.html.haml1
-rw-r--r--app/views/projects/merge_requests/index.html.haml1
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml1
-rw-r--r--app/views/projects/merge_requests/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/open/_not_allowed.html.haml4
-rw-r--r--app/views/projects/milestones/_header_title.html.haml1
-rw-r--r--app/views/projects/milestones/edit.html.haml1
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/new.html.haml1
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml1
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml18
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml20
-rw-r--r--app/views/projects/notes/_discussion.html.haml47
-rw-r--r--app/views/projects/notes/_form.html.haml1
-rw-r--r--app/views/projects/notes/_note.html.haml11
-rw-r--r--app/views/projects/notes/_notes.html.haml9
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml16
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml25
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml28
-rw-r--r--app/views/projects/notes/discussions/_diff_with_notes.html.haml30
-rw-r--r--app/views/projects/notes/discussions/_notes.html.haml7
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml14
-rw-r--r--app/views/projects/pipelines/_head.html.haml14
-rw-r--r--app/views/projects/pipelines/_info.html.haml37
-rw-r--r--app/views/projects/pipelines/index.html.haml58
-rw-r--r--app/views/projects/pipelines/new.html.haml21
-rw-r--r--app/views/projects/pipelines/show.html.haml8
-rw-r--r--app/views/projects/project_members/_header_title.html.haml1
-rw-r--r--app/views/projects/project_members/import.html.haml1
-rw-r--r--app/views/projects/project_members/index.html.haml1
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml1
-rw-r--r--app/views/projects/repositories/_feed.html.haml2
-rw-r--r--app/views/projects/runners/_form.html.haml32
-rw-r--r--app/views/projects/runners/_runner.html.haml2
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml2
-rw-r--r--app/views/projects/runners/edit.html.haml27
-rw-r--r--app/views/projects/runners/show.html.haml51
-rw-r--r--app/views/projects/services/_form.html.haml32
-rw-r--r--app/views/projects/services/index.html.haml52
-rw-r--r--app/views/projects/show.atom.builder4
-rw-r--r--app/views/projects/show.html.haml78
-rw-r--r--app/views/projects/snippets/_actions.html.haml38
-rw-r--r--app/views/projects/snippets/_header_title.html.haml1
-rw-r--r--app/views/projects/snippets/edit.html.haml1
-rw-r--r--app/views/projects/snippets/index.html.haml1
-rw-r--r--app/views/projects/snippets/new.html.haml1
-rw-r--r--app/views/projects/snippets/show.html.haml9
-rw-r--r--app/views/projects/tags/destroy.js.haml1
-rw-r--r--app/views/projects/tags/index.html.haml1
-rw-r--r--app/views/projects/tags/new.html.haml3
-rw-r--r--app/views/projects/tags/show.html.haml9
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/projects/triggers/index.html.haml22
-rw-r--r--app/views/projects/variables/_content.html.haml8
-rw-r--r--app/views/projects/variables/_form.html.haml10
-rw-r--r--app/views/projects/variables/_table.html.haml25
-rw-r--r--app/views/projects/variables/index.html.haml17
-rw-r--r--app/views/projects/variables/show.html.haml41
-rw-r--r--app/views/projects/wikis/_header_title.html.haml1
-rw-r--r--app/views/projects/wikis/edit.html.haml1
-rw-r--r--app/views/projects/wikis/empty.html.haml1
-rw-r--r--app/views/projects/wikis/git_access.html.haml1
-rw-r--r--app/views/projects/wikis/history.html.haml1
-rw-r--r--app/views/projects/wikis/pages.html.haml1
-rw-r--r--app/views/projects/wikis/show.html.haml1
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_note.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml4
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/groups/_list.html.haml2
-rw-r--r--app/views/shared/issuable/_filter.html.haml6
-rw-r--r--app/views/shared/issuable/_form.html.haml93
-rw-r--r--app/views/shared/issuable/_search_form.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml24
-rw-r--r--app/views/shared/milestones/_participants_tab.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml5
-rw-r--r--app/views/shared/snippets/_header.html.haml23
-rw-r--r--app/views/snippets/_actions.html.haml38
-rw-r--r--app/views/snippets/show.html.haml7
-rw-r--r--app/views/users/calendar.html.haml19
-rw-r--r--app/views/users/calendar_activities.html.haml42
-rw-r--r--app/views/users/show.atom.builder4
-rw-r--r--app/views/users/show.html.haml14
-rw-r--r--app/workers/emails_on_push_worker.rb44
-rw-r--r--app/workers/repository_fork_worker.rb6
-rw-r--r--app/workers/repository_import_worker.rb3
-rw-r--r--config/application.rb32
-rw-r--r--config/boot.rb2
-rw-r--r--config/environments/development.rb1
-rw-r--r--config/environments/test.rb2
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--config/initializers/1_settings.rb46
-rw-r--r--config/initializers/5_backend.rb6
-rw-r--r--config/initializers/carrierwave.rb4
-rw-r--r--config/initializers/devise.rb2
-rw-r--r--config/initializers/devise_async.rb1
-rw-r--r--config/initializers/doorkeeper.rb4
-rw-r--r--config/initializers/health_check.rb3
-rw-r--r--config/initializers/metrics.rb3
-rw-r--r--config/initializers/monkey_patch.rb48
-rw-r--r--config/initializers/omniauth.rb2
-rw-r--r--config/initializers/premailer.rb2
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/routes.rb63
-rw-r--r--db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb4
-rw-r--r--db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb4
-rw-r--r--db/migrate/20160407120251_add_images_enabled_for_project.rb5
-rw-r--r--db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb5
-rw-r--r--db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb13
-rw-r--r--db/migrate/20160508215820_add_type_to_notes.rb5
-rw-r--r--db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb5
-rw-r--r--db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb5
-rw-r--r--db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb12
-rw-r--r--db/migrate/20160525205328_remove_main_language_from_projects.rb21
-rw-r--r--db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb13
-rw-r--r--db/migrate/20160528043124_add_users_state_index.rb9
-rw-r--r--db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb9
-rw-r--r--db/schema.rb55
-rw-r--r--doc/README.md3
-rw-r--r--doc/administration/container_registry.md375
-rw-r--r--doc/administration/environment_variables.md2
-rw-r--r--doc/administration/high_availability/README.md4
-rw-r--r--doc/administration/high_availability/load_balancer.md2
-rw-r--r--doc/administration/high_availability/nfs.md6
-rw-r--r--doc/administration/high_availability/redis.md2
-rw-r--r--doc/administration/img/high_availability/active-active-diagram.pngbin0 -> 29607 bytes
-rw-r--r--doc/administration/img/high_availability/active-passive-diagram.pngbin0 -> 24246 bytes
-rw-r--r--doc/administration/repository_checks.md4
-rw-r--r--doc/administration/troubleshooting/sidekiq.md8
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/build_triggers.md12
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/issues.md15
-rw-r--r--doc/api/labels.md72
-rw-r--r--doc/api/merge_requests.md22
-rw-r--r--doc/api/notes.md10
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/api/runners.md4
-rw-r--r--doc/api/services.md8
-rw-r--r--doc/api/settings.md7
-rw-r--r--doc/ci/examples/deployment/README.md (renamed from doc/ci/deployment/README.md)0
-rw-r--r--doc/ci/quick_start/README.md4
-rw-r--r--doc/ci/runners/README.md8
-rw-r--r--doc/ci/triggers/README.md2
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/container_registry/README.md113
-rw-r--r--doc/container_registry/img/container_registry.pngbin0 -> 354050 bytes
-rw-r--r--doc/container_registry/img/project_feature.pngbin0 -> 392842 bytes
-rw-r--r--doc/development/doc_styleguide.md4
-rw-r--r--doc/development/instrumentation.md129
-rw-r--r--doc/development/migration_style_guide.md48
-rw-r--r--doc/development/testing.md2
-rw-r--r--doc/development/ui_guide.md48
-rw-r--r--doc/gitlab-basics/create-issue.md2
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/hooks/custom_hooks.md2
-rw-r--r--doc/install/installation.md8
-rw-r--r--doc/install/relative_url.md2
-rw-r--r--doc/install/requirements.md3
-rw-r--r--doc/integration/README.md2
-rw-r--r--doc/integration/cas.md19
-rw-r--r--doc/integration/google.md4
-rw-r--r--doc/integration/img/enabled-oauth-sign-in-sources.pngbin0 -> 49081 bytes
-rw-r--r--doc/integration/omniauth.md15
-rw-r--r--doc/intro/README.md2
-rw-r--r--doc/logs/logs.md8
-rw-r--r--doc/markdown/markdown.md49
-rw-r--r--doc/migrate_ci_to_ce/README.md2
-rw-r--r--doc/monitoring/health_check.md66
-rw-r--r--doc/monitoring/img/health_check_token.pngbin0 -> 10884 bytes
-rw-r--r--doc/operations/moving_repositories.md8
-rw-r--r--doc/permissions/permissions.md3
-rw-r--r--doc/raketasks/README.md2
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/user_email_confirmation.md7
-rw-r--r--doc/update/8.6-to-8.7.md8
-rw-r--r--doc/update/8.7-to-8.8.md162
-rw-r--r--doc/update/README.md6
-rw-r--r--doc/update/patch_versions.md4
-rw-r--r--doc/web_hooks/web_hooks.md68
-rw-r--r--doc/workflow/gitlab_flow.md4
-rw-r--r--doc/workflow/groups.md2
-rw-r--r--doc/workflow/importing/import_projects_from_github.md2
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md2
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md6
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--features/project/active_tab.feature55
-rw-r--r--features/project/builds/summary.feature1
-rw-r--r--features/project/commits/tags.feature46
-rw-r--r--features/project/create.feature16
-rw-r--r--features/project/project.feature9
-rw-r--r--features/project/shortcuts.feature8
-rw-r--r--features/steps/admin/active_tab.rb10
-rw-r--r--features/steps/admin/users.rb2
-rw-r--r--features/steps/dashboard/active_tab.rb6
-rw-r--r--features/steps/dashboard/dashboard.rb2
-rw-r--r--features/steps/dashboard/issues.rb2
-rw-r--r--features/steps/dashboard/merge_requests.rb2
-rw-r--r--features/steps/dashboard/shortcuts.rb3
-rw-r--r--features/steps/dashboard/todos.rb4
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/active_tab.rb32
-rw-r--r--features/steps/project/builds/summary.rb4
-rw-r--r--features/steps/project/commits/commits.rb6
-rw-r--r--features/steps/project/commits/tags.rb90
-rw-r--r--features/steps/project/create.rb26
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/hooks.rb2
-rw-r--r--features/steps/project/issues/award_emoji.rb4
-rw-r--r--features/steps/project/issues/issues.rb6
-rw-r--r--features/steps/project/issues/labels.rb4
-rw-r--r--features/steps/project/merge_requests.rb10
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/project/project_find_file.rb4
-rw-r--r--features/steps/project/project_milestone.rb2
-rw-r--r--features/steps/project/project_shortcuts.rb1
-rw-r--r--features/steps/project/snippets.rb4
-rw-r--r--features/steps/project/source/browse_files.rb16
-rw-r--r--features/steps/project/team_management.rb2
-rw-r--r--features/steps/shared/active_tab.rb28
-rw-r--r--features/steps/shared/diff_note.rb12
-rw-r--r--features/steps/shared/issuable.rb8
-rw-r--r--features/steps/shared/note.rb2
-rw-r--r--features/steps/shared/project.rb2
-rw-r--r--features/steps/shared/project_tab.rb20
-rw-r--r--features/steps/shared/shortcuts.rb2
-rw-r--r--features/steps/shared/sidebar_active_tab.rb35
-rw-r--r--features/steps/snippets/snippets.rb4
-rw-r--r--features/steps/user.rb2
-rw-r--r--generator_templates/active_record/migration/create_table_migration.rb35
-rw-r--r--generator_templates/active_record/migration/migration.rb55
-rw-r--r--lib/api/api.rb71
-rw-r--r--lib/api/api_guard.rb270
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb27
-rw-r--r--lib/api/gitignores.rb29
-rw-r--r--lib/api/groups.rb3
-rw-r--r--lib/api/helpers.rb15
-rw-r--r--lib/api/issues.rb39
-rw-r--r--lib/api/labels.rb6
-rw-r--r--lib/api/licenses.rb14
-rw-r--r--lib/api/merge_requests.rb36
-rw-r--r--lib/api/notes.rb47
-rw-r--r--lib/api/projects.rb7
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/subscriptions.rb60
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/backup/manager.rb17
-rw-r--r--lib/backup/registry.rb13
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb12
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb20
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb20
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb14
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb26
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb7
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb46
-rw-r--r--lib/banzai/filter/redactor_filter.rb31
-rw-r--r--lib/banzai/filter/reference_filter.rb24
-rw-r--r--lib/banzai/filter/reference_gatherer_filter.rb65
-rw-r--r--lib/banzai/filter/sanitization_filter.rb2
-rw-r--r--lib/banzai/filter/snippet_reference_filter.rb2
-rw-r--r--lib/banzai/filter/upload_link_filter.rb8
-rw-r--r--lib/banzai/filter/user_reference_filter.rb44
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb11
-rw-r--r--lib/banzai/lazy_reference.rb25
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/reference_extraction_pipeline.rb11
-rw-r--r--lib/banzai/reference_extractor.rb48
-rw-r--r--lib/banzai/reference_parser.rb14
-rw-r--r--lib/banzai/reference_parser/base_parser.rb204
-rw-r--r--lib/banzai/reference_parser/commit_parser.rb34
-rw-r--r--lib/banzai/reference_parser/commit_range_parser.rb38
-rw-r--r--lib/banzai/reference_parser/external_issue_parser.rb25
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb40
-rw-r--r--lib/banzai/reference_parser/label_parser.rb11
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb11
-rw-r--r--lib/banzai/reference_parser/milestone_parser.rb11
-rw-r--r--lib/banzai/reference_parser/snippet_parser.rb11
-rw-r--r--lib/banzai/reference_parser/user_parser.rb92
-rw-r--r--lib/ci/ansi2html.rb87
-rw-r--r--lib/ci/api/api.rb10
-rw-r--r--lib/ci/api/runners.rb18
-rw-r--r--lib/ci/charts.rb3
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb4
-rw-r--r--lib/container_registry/blob.rb48
-rw-r--r--lib/container_registry/client.rb61
-rw-r--r--lib/container_registry/config.rb16
-rw-r--r--lib/container_registry/registry.rb21
-rw-r--r--lib/container_registry/repository.rb48
-rw-r--r--lib/container_registry/tag.rb77
-rw-r--r--lib/event_filter.rb2
-rw-r--r--lib/gitlab.rb2
-rw-r--r--lib/gitlab/backend/shell.rb2
-rw-r--r--lib/gitlab/bitbucket_import/client.rb2
-rw-r--r--lib/gitlab/bitbucket_import/project_creator.rb7
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/current_settings.rb23
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--lib/gitlab/database/migration_helpers.rb142
-rw-r--r--lib/gitlab/diff/inline_diff_marker.rb36
-rw-r--r--lib/gitlab/diff/parser.rb16
-rw-r--r--lib/gitlab/email/message/repository_push.rb11
-rw-r--r--lib/gitlab/email/reply_parser.rb2
-rw-r--r--lib/gitlab/fogbugz_import/project_creator.rb9
-rw-r--r--lib/gitlab/github_import/branch_formatter.rb29
-rw-r--r--lib/gitlab/github_import/importer.rb76
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb54
-rw-r--r--lib/gitlab/gitignore.rb56
-rw-r--r--lib/gitlab/gitlab_import/importer.rb8
-rw-r--r--lib/gitlab/google_code_import/project_creator.rb9
-rw-r--r--lib/gitlab/lazy.rb34
-rw-r--r--lib/gitlab/markup_helper.rb2
-rw-r--r--lib/gitlab/metrics/instrumentation.rb2
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb12
-rw-r--r--lib/gitlab/middleware/go.rb2
-rw-r--r--lib/gitlab/middleware/rails_queue_duration.rb24
-rw-r--r--lib/gitlab/project_search_results.rb2
-rw-r--r--lib/gitlab/redis.rb8
-rw-r--r--lib/gitlab/reference_extractor.rb19
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/gitlab/sanitizers/svg.rb8
-rw-r--r--lib/gitlab/sanitizers/svg/whitelist.rb170
-rw-r--r--lib/gitlab/url_builder.rb2
-rw-r--r--lib/gitlab/url_sanitizer.rb (renamed from lib/gitlab/import_url.rb)17
-rw-r--r--lib/gitlab/visibility_level.rb7
-rw-r--r--lib/json_web_token/rsa_token.rb42
-rw-r--r--lib/json_web_token/token.rb46
-rw-r--r--lib/support/nginx/registry-ssl53
-rw-r--r--lib/tasks/auto_annotate_models.rake44
-rw-r--r--lib/tasks/gitlab/backup.rake29
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--lib/tasks/gitlab/db.rake14
-rw-r--r--lib/tasks/gitlab/update_gitignore.rake46
-rw-r--r--lib/tasks/rubocop.rake1
-rw-r--r--public/robots.txt1
-rw-r--r--shared/registry/.gitkeep0
-rw-r--r--spec/config/mail_room_spec.rb2
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb76
-rw-r--r--spec/controllers/health_check_controller_spec.rb105
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb4
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb4
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb4
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb2
-rw-r--r--spec/controllers/projects/notification_settings_controller_spec.rb14
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb45
-rw-r--r--spec/controllers/registrations_controller_spec.rb33
-rw-r--r--spec/controllers/sessions_controller_spec.rb29
-rw-r--r--spec/controllers/users_controller_spec.rb22
-rw-r--r--spec/factories/abuse_reports.rb12
-rw-r--r--spec/factories/broadcast_messages.rb14
-rw-r--r--spec/factories/forked_project_links.rb11
-rw-r--r--spec/factories/label_links.rb12
-rw-r--r--spec/factories/labels.rb13
-rw-r--r--spec/factories/lfs_objects.rb12
-rw-r--r--spec/factories/lfs_objects_projects.rb11
-rw-r--r--spec/factories/merge_requests.rb29
-rw-r--r--spec/factories/notes.rb45
-rw-r--r--spec/factories/oauth_access_tokens.rb15
-rw-r--r--spec/factories/projects.rb49
-rw-r--r--spec/factories/releases.rb12
-rw-r--r--spec/factories/todos.rb22
-rw-r--r--spec/factories_spec.rb4
-rw-r--r--spec/features/admin/admin_builds_spec.rb1
-rw-r--r--spec/features/admin/admin_health_check_spec.rb55
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb5
-rw-r--r--spec/features/builds_spec.rb4
-rw-r--r--spec/features/commits_spec.rb10
-rw-r--r--spec/features/container_registry_spec.rb44
-rw-r--r--spec/features/issues/filter_issues_spec.rb176
-rw-r--r--spec/features/issues/move_spec.rb16
-rw-r--r--spec/features/issues/note_polling_spec.rb5
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues_spec.rb99
-rw-r--r--spec/features/login_spec.rb10
-rw-r--r--spec/features/markdown_spec.rb4
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb58
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb161
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb12
-rw-r--r--spec/features/participants_autocomplete_spec.rb6
-rw-r--r--spec/features/pipelines_spec.rb189
-rw-r--r--spec/features/projects/badges/list_spec.rb5
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb63
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb30
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb8
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb4
-rw-r--r--spec/features/projects/shortcuts_spec.rb (renamed from spec/features/project/shortcuts_spec.rb)4
-rw-r--r--spec/features/runners_spec.rb37
-rw-r--r--spec/features/signup_spec.rb45
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb62
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb41
-rw-r--r--spec/features/tags/master_updates_tag_spec.rb42
-rw-r--r--spec/features/tags/master_views_tags_spec.rb73
-rw-r--r--spec/features/task_lists_spec.rb5
-rw-r--r--spec/features/todos/target_state_spec.rb65
-rw-r--r--spec/features/todos/todos_spec.rb21
-rw-r--r--spec/features/variables_spec.rb61
-rw-r--r--spec/finders/issues_finder_spec.rb180
-rw-r--r--spec/fixtures/container_registry/config_blob.json1
-rw-r--r--spec/fixtures/container_registry/tag_manifest.json1
-rw-r--r--spec/fixtures/markdown.md.erb23
-rw-r--r--spec/helpers/auth_helper_spec.rb47
-rw-r--r--spec/helpers/diff_helper_spec.rb4
-rw-r--r--spec/helpers/events_helper_spec.rb95
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb10
-rw-r--r--spec/javascripts/fixtures/right_sidebar.html.haml13
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_graph_spec.js (renamed from spec/javascripts/stat_graph_contributors_graph_spec.js)2
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js (renamed from spec/javascripts/stat_graph_contributors_util_spec.js)2
-rw-r--r--spec/javascripts/graphs/stat_graph_spec.js (renamed from spec/javascripts/stat_graph_spec.js)2
-rw-r--r--spec/javascripts/project_title_spec.js.coffee1
-rw-r--r--spec/javascripts/right_sidebar_spec.js.coffee69
-rw-r--r--spec/lib/award_emoji_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb68
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb25
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb145
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb38
-rw-r--r--spec/lib/banzai/filter/reference_gatherer_filter_spec.rb87
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb18
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb35
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb85
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb237
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb113
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb120
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb62
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb79
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb31
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb30
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb31
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb31
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb189
-rw-r--r--spec/lib/ci/ansi2html_spec.rb120
-rw-r--r--spec/lib/ci/charts_spec.rb7
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb10
-rw-r--r--spec/lib/container_registry/blob_spec.rb61
-rw-r--r--spec/lib/container_registry/registry_spec.rb28
-rw-r--r--spec/lib/container_registry/repository_spec.rb65
-rw-r--r--spec/lib/container_registry/tag_spec.rb89
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb4
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb128
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb4
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb4
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb8
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb71
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb46
-rw-r--r--spec/lib/gitlab/gitignore_spec.rb40
-rw-r--r--spec/lib/gitlab/import_url_spec.rb21
-rw-r--r--spec/lib/gitlab/lazy_spec.rb37
-rw-r--r--spec/lib/gitlab/lfs/lfs_router_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics/sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb16
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb31
-rw-r--r--spec/lib/gitlab/note_data_builder_spec.rb71
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb6
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb68
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb43
-rw-r--r--spec/lib/json_web_token/token_spec.rb18
-rw-r--r--spec/mailers/notify_spec.rb54
-rw-r--r--spec/mailers/previews/devise_mailer_preview.rb11
-rw-r--r--spec/mailers/shared/notify.rb4
-rw-r--r--spec/models/ability_spec.rb117
-rw-r--r--spec/models/abuse_report_spec.rb12
-rw-r--r--spec/models/application_setting_spec.rb55
-rw-r--r--spec/models/broadcast_message_spec.rb14
-rw-r--r--spec/models/build_spec.rb98
-rw-r--r--spec/models/ci/commit_spec.rb21
-rw-r--r--spec/models/ci/runner_project_spec.rb17
-rw-r--r--spec/models/ci/runner_spec.rb51
-rw-r--r--spec/models/ci/trigger_spec.rb13
-rw-r--r--spec/models/ci/variable_spec.rb16
-rw-r--r--spec/models/commit_range_spec.rb34
-rw-r--r--spec/models/commit_spec.rb40
-rw-r--r--spec/models/commit_status_spec.rb34
-rw-r--r--spec/models/concerns/issuable_spec.rb54
-rw-r--r--spec/models/concerns/participable_spec.rb83
-rw-r--r--spec/models/concerns/subscribable_spec.rb10
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb6
-rw-r--r--spec/models/deploy_key_spec.rb15
-rw-r--r--spec/models/deploy_keys_project_spec.rb11
-rw-r--r--spec/models/email_spec.rb11
-rw-r--r--spec/models/event_spec.rb16
-rw-r--r--spec/models/forked_project_link_spec.rb11
-rw-r--r--spec/models/generic_commit_status_spec.rb38
-rw-r--r--spec/models/group_spec.rb15
-rw-r--r--spec/models/hooks/service_hook_spec.rb4
-rw-r--r--spec/models/hooks/system_hook_spec.rb20
-rw-r--r--spec/models/hooks/web_hook_spec.rb4
-rw-r--r--spec/models/identity_spec.rb12
-rw-r--r--spec/models/issue_spec.rb77
-rw-r--r--spec/models/key_spec.rb15
-rw-r--r--spec/models/label_link_spec.rb12
-rw-r--r--spec/models/label_spec.rb21
-rw-r--r--spec/models/legacy_diff_note_spec.rb76
-rw-r--r--spec/models/member_spec.rb19
-rw-r--r--spec/models/members/project_member_spec.rb42
-rw-r--r--spec/models/merge_request_spec.rb99
-rw-r--r--spec/models/milestone_spec.rb56
-rw-r--r--spec/models/namespace_spec.rb29
-rw-r--r--spec/models/note_spec.rb187
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb151
-rw-r--r--spec/models/project_services/slack_service/build_message_spec.rb23
-rw-r--r--spec/models/project_services/slack_service/issue_message_spec.rb11
-rw-r--r--spec/models/project_services/slack_service/note_message_spec.rb4
-rw-r--r--spec/models/project_services/slack_service_spec.rb68
-rw-r--r--spec/models/project_snippet_spec.rb16
-rw-r--r--spec/models/project_spec.rb132
-rw-r--r--spec/models/project_wiki_spec.rb16
-rw-r--r--spec/models/protected_branch_spec.rb12
-rw-r--r--spec/models/release_spec.rb12
-rw-r--r--spec/models/repository_spec.rb46
-rw-r--r--spec/models/service_spec.rb21
-rw-r--r--spec/models/snippet_spec.rb43
-rw-r--r--spec/models/todo_spec.rb18
-rw-r--r--spec/models/user_spec.rb89
-rw-r--r--spec/requests/api/builds_spec.rb4
-rw-r--r--spec/requests/api/commit_statuses_spec.rb (renamed from spec/requests/api/commit_status_spec.rb)2
-rw-r--r--spec/requests/api/gitignores_spec.rb29
-rw-r--r--spec/requests/api/group_members_spec.rb10
-rw-r--r--spec/requests/api/groups_spec.rb21
-rw-r--r--spec/requests/api/issues_spec.rb33
-rw-r--r--spec/requests/api/labels_spec.rb82
-rw-r--r--spec/requests/api/licenses_spec.rb12
-rw-r--r--spec/requests/api/merge_requests_spec.rb28
-rw-r--r--spec/requests/api/notes_spec.rb71
-rw-r--r--spec/requests/api/project_members_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb21
-rw-r--r--spec/requests/api/runners_spec.rb15
-rw-r--r--spec/requests/api/system_hooks_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb2
-rw-r--r--spec/requests/ci/api/builds_spec.rb40
-rw-r--r--spec/requests/ci/api/runners_spec.rb83
-rw-r--r--spec/requests/jwt_controller_spec.rb72
-rw-r--r--spec/routing/admin_routing_spec.rb7
-rw-r--r--spec/routing/routing_spec.rb51
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb242
-rw-r--r--spec/services/create_commit_builds_service_spec.rb2
-rw-r--r--spec/services/git_push_service_spec.rb43
-rw-r--r--spec/services/groups/create_service_spec.rb4
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb9
-rw-r--r--spec/services/issues/create_service_spec.rb6
-rw-r--r--spec/services/issues/move_service_spec.rb6
-rw-r--r--spec/services/issues/update_service_spec.rb24
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb81
-rw-r--r--spec/services/merge_requests/create_service_spec.rb4
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb15
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb16
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb28
-rw-r--r--spec/services/merge_requests/update_service_spec.rb8
-rw-r--r--spec/services/notification_service_spec.rb30
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/destroy_service_spec.rb31
-rw-r--r--spec/services/projects/fork_service_spec.rb27
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb11
-rw-r--r--spec/services/system_note_service_spec.rb26
-rw-r--r--spec/services/todo_service_spec.rb38
-rw-r--r--spec/support/filter_spec_helper.rb3
-rw-r--r--spec/support/jira_service_helper.rb10
-rw-r--r--spec/support/login_helpers.rb6
-rw-r--r--spec/support/markdown_feature.rb6
-rw-r--r--spec/support/matchers/markdown_matchers.rb12
-rw-r--r--spec/support/reference_parser_helpers.rb5
-rw-r--r--spec/support/stub_gitlab_calls.rb37
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb32
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb62
-rw-r--r--spec/teaspoon_env.rb50
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb65
-rw-r--r--spec/workers/post_receive_spec.rb16
-rw-r--r--spec/workers/repository_import_worker_spec.rb26
-rwxr-xr-xvendor/assets/javascripts/jquery.scrollTo.js210
-rw-r--r--vendor/assets/stylesheets/animate.css11
-rw-r--r--vendor/gitignore/Actionscript.gitignore19
-rw-r--r--vendor/gitignore/Ada.gitignore5
-rw-r--r--vendor/gitignore/Agda.gitignore1
-rw-r--r--vendor/gitignore/Android.gitignore39
-rw-r--r--vendor/gitignore/AppEngine.gitignore2
-rw-r--r--vendor/gitignore/AppceleratorTitanium.gitignore3
-rw-r--r--vendor/gitignore/ArchLinuxPackages.gitignore13
-rw-r--r--vendor/gitignore/Autotools.gitignore18
-rw-r--r--vendor/gitignore/C++.gitignore28
-rw-r--r--vendor/gitignore/C.gitignore33
-rw-r--r--vendor/gitignore/CFWheels.gitignore12
-rw-r--r--vendor/gitignore/CMake.gitignore6
-rw-r--r--vendor/gitignore/CUDA.gitignore6
-rw-r--r--vendor/gitignore/CakePHP.gitignore25
-rw-r--r--vendor/gitignore/ChefCookbook.gitignore9
l---------vendor/gitignore/Clojure.gitignore1
-rw-r--r--vendor/gitignore/CodeIgniter.gitignore6
-rw-r--r--vendor/gitignore/CommonLisp.gitignore3
-rw-r--r--vendor/gitignore/Composer.gitignore6
-rw-r--r--vendor/gitignore/Concrete5.gitignore4
-rw-r--r--vendor/gitignore/Coq.gitignore3
-rw-r--r--vendor/gitignore/CraftCMS.gitignore3
-rw-r--r--vendor/gitignore/D.gitignore20
-rw-r--r--vendor/gitignore/DM.gitignore5
-rw-r--r--vendor/gitignore/Dart.gitignore27
-rw-r--r--vendor/gitignore/Delphi.gitignore66
-rw-r--r--vendor/gitignore/Drupal.gitignore36
-rw-r--r--vendor/gitignore/EPiServer.gitignore4
-rw-r--r--vendor/gitignore/Eagle.gitignore44
-rw-r--r--vendor/gitignore/Elisp.gitignore5
-rw-r--r--vendor/gitignore/Elixir.gitignore5
-rw-r--r--vendor/gitignore/Elm.gitignore4
-rw-r--r--vendor/gitignore/Erlang.gitignore10
-rw-r--r--vendor/gitignore/ExpressionEngine.gitignore19
-rw-r--r--vendor/gitignore/ExtJs.gitignore4
-rw-r--r--vendor/gitignore/Fancy.gitignore2
-rw-r--r--vendor/gitignore/Finale.gitignore13
-rw-r--r--vendor/gitignore/ForceDotCom.gitignore4
l---------vendor/gitignore/Fortran.gitignore1
-rw-r--r--vendor/gitignore/FuelPHP.gitignore21
-rw-r--r--vendor/gitignore/GWT.gitignore28
-rw-r--r--vendor/gitignore/Gcov.gitignore5
-rw-r--r--vendor/gitignore/GitBook.gitignore16
-rw-r--r--vendor/gitignore/Global/Anjuta.gitignore3
-rw-r--r--vendor/gitignore/Global/Archives.gitignore27
-rw-r--r--vendor/gitignore/Global/BricxCC.gitignore4
-rw-r--r--vendor/gitignore/Global/CVS.gitignore4
-rw-r--r--vendor/gitignore/Global/Calabash.gitignore10
-rw-r--r--vendor/gitignore/Global/Cloud9.gitignore3
-rw-r--r--vendor/gitignore/Global/CodeKit.gitignore3
-rw-r--r--vendor/gitignore/Global/DartEditor.gitignore2
-rw-r--r--vendor/gitignore/Global/Dreamweaver.gitignore7
-rw-r--r--vendor/gitignore/Global/Dropbox.gitignore4
-rw-r--r--vendor/gitignore/Global/Eclipse.gitignore51
-rw-r--r--vendor/gitignore/Global/EiffelStudio.gitignore2
-rw-r--r--vendor/gitignore/Global/Emacs.gitignore42
-rw-r--r--vendor/gitignore/Global/Ensime.gitignore4
-rw-r--r--vendor/gitignore/Global/Espresso.gitignore1
-rw-r--r--vendor/gitignore/Global/FlexBuilder.gitignore3
-rw-r--r--vendor/gitignore/Global/GPG.gitignore2
-rw-r--r--vendor/gitignore/Global/IPythonNotebook.gitignore2
-rw-r--r--vendor/gitignore/Global/JDeveloper.gitignore13
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore44
-rw-r--r--vendor/gitignore/Global/KDevelop4.gitignore2
-rw-r--r--vendor/gitignore/Global/Kate.gitignore3
-rw-r--r--vendor/gitignore/Global/Lazarus.gitignore30
-rw-r--r--vendor/gitignore/Global/LibreOffice.gitignore2
-rw-r--r--vendor/gitignore/Global/Linux.gitignore10
-rw-r--r--vendor/gitignore/Global/LyX.gitignore4
-rw-r--r--vendor/gitignore/Global/Matlab.gitignore19
-rw-r--r--vendor/gitignore/Global/Mercurial.gitignore6
-rw-r--r--vendor/gitignore/Global/MicrosoftOffice.gitignore16
-rw-r--r--vendor/gitignore/Global/ModelSim.gitignore23
-rw-r--r--vendor/gitignore/Global/Momentics.gitignore8
-rw-r--r--vendor/gitignore/Global/MonoDevelop.gitignore8
-rw-r--r--vendor/gitignore/Global/NetBeans.gitignore7
-rw-r--r--vendor/gitignore/Global/Ninja.gitignore2
-rw-r--r--vendor/gitignore/Global/NotepadPP.gitignore2
-rw-r--r--vendor/gitignore/Global/OSX.gitignore24
-rw-r--r--vendor/gitignore/Global/Otto.gitignore1
-rw-r--r--vendor/gitignore/Global/Redcar.gitignore1
-rw-r--r--vendor/gitignore/Global/Redis.gitignore3
-rw-r--r--vendor/gitignore/Global/SBT.gitignore9
-rw-r--r--vendor/gitignore/Global/SVN.gitignore1
-rw-r--r--vendor/gitignore/Global/SlickEdit.gitignore11
-rw-r--r--vendor/gitignore/Global/SublimeText.gitignore14
-rw-r--r--vendor/gitignore/Global/SynopsysVCS.gitignore36
-rw-r--r--vendor/gitignore/Global/Tags.gitignore16
-rw-r--r--vendor/gitignore/Global/TextMate.gitignore3
-rw-r--r--vendor/gitignore/Global/TortoiseGit.gitignore2
-rw-r--r--vendor/gitignore/Global/Vagrant.gitignore1
-rw-r--r--vendor/gitignore/Global/Vim.gitignore10
-rw-r--r--vendor/gitignore/Global/VirtualEnv.gitignore12
-rw-r--r--vendor/gitignore/Global/VisualStudioCode.gitignore2
-rw-r--r--vendor/gitignore/Global/WebMethods.gitignore14
-rw-r--r--vendor/gitignore/Global/Windows.gitignore18
-rw-r--r--vendor/gitignore/Global/Xcode.gitignore23
-rw-r--r--vendor/gitignore/Global/XilinxISE.gitignore67
-rw-r--r--vendor/gitignore/Go.gitignore24
-rw-r--r--vendor/gitignore/Gradle.gitignore14
-rw-r--r--vendor/gitignore/Grails.gitignore33
-rw-r--r--vendor/gitignore/Haskell.gitignore18
-rw-r--r--vendor/gitignore/IGORPro.gitignore5
-rw-r--r--vendor/gitignore/Idris.gitignore2
-rw-r--r--vendor/gitignore/Java.gitignore12
-rw-r--r--vendor/gitignore/Jboss.gitignore19
-rw-r--r--vendor/gitignore/Jekyll.gitignore3
-rw-r--r--vendor/gitignore/Joomla.gitignore546
-rw-r--r--vendor/gitignore/KiCad.gitignore20
-rw-r--r--vendor/gitignore/Kohana.gitignore2
-rw-r--r--vendor/gitignore/LabVIEW.gitignore16
-rw-r--r--vendor/gitignore/Laravel.gitignore16
-rw-r--r--vendor/gitignore/Leiningen.gitignore12
-rw-r--r--vendor/gitignore/LemonStand.gitignore21
-rw-r--r--vendor/gitignore/Lilypond.gitignore6
-rw-r--r--vendor/gitignore/Lithium.gitignore2
-rw-r--r--vendor/gitignore/Lua.gitignore41
-rw-r--r--vendor/gitignore/Magento.gitignore104
-rw-r--r--vendor/gitignore/Maven.gitignore9
-rw-r--r--vendor/gitignore/Mercury.gitignore13
-rw-r--r--vendor/gitignore/MetaProgrammingSystem.gitignore16
-rw-r--r--vendor/gitignore/Nanoc.gitignore10
-rw-r--r--vendor/gitignore/Nim.gitignore1
-rw-r--r--vendor/gitignore/Node.gitignore37
-rw-r--r--vendor/gitignore/OCaml.gitignore20
-rw-r--r--vendor/gitignore/Objective-C.gitignore51
-rw-r--r--vendor/gitignore/Opa.gitignore13
-rw-r--r--vendor/gitignore/OpenCart.gitignore13
-rw-r--r--vendor/gitignore/OracleForms.gitignore8
-rw-r--r--vendor/gitignore/Packer.gitignore5
-rw-r--r--vendor/gitignore/Perl.gitignore20
-rw-r--r--vendor/gitignore/Phalcon.gitignore2
-rw-r--r--vendor/gitignore/PlayFramework.gitignore15
-rw-r--r--vendor/gitignore/Plone.gitignore18
-rw-r--r--vendor/gitignore/Prestashop.gitignore32
-rw-r--r--vendor/gitignore/Processing.gitignore7
-rw-r--r--vendor/gitignore/Python.gitignore89
-rw-r--r--vendor/gitignore/Qooxdoo.gitignore5
-rw-r--r--vendor/gitignore/Qt.gitignore38
-rw-r--r--vendor/gitignore/R.gitignore33
-rw-r--r--vendor/gitignore/README.md14
-rw-r--r--vendor/gitignore/ROS.gitignore47
-rw-r--r--vendor/gitignore/Rails.gitignore38
-rw-r--r--vendor/gitignore/RhodesRhomobile.gitignore9
-rw-r--r--vendor/gitignore/Ruby.gitignore50
-rw-r--r--vendor/gitignore/Rust.gitignore7
-rw-r--r--vendor/gitignore/SCons.gitignore2
-rw-r--r--vendor/gitignore/Sass.gitignore2
-rw-r--r--vendor/gitignore/Scala.gitignore17
-rw-r--r--vendor/gitignore/Scheme.gitignore7
-rw-r--r--vendor/gitignore/Scrivener.gitignore7
-rw-r--r--vendor/gitignore/Sdcc.gitignore8
-rw-r--r--vendor/gitignore/SeamGen.gitignore26
-rw-r--r--vendor/gitignore/SketchUp.gitignore1
-rw-r--r--vendor/gitignore/Smalltalk.gitignore18
-rw-r--r--vendor/gitignore/Stella.gitignore12
-rw-r--r--vendor/gitignore/SugarCRM.gitignore25
-rw-r--r--vendor/gitignore/Swift.gitignore63
-rw-r--r--vendor/gitignore/Symfony.gitignore48
-rw-r--r--vendor/gitignore/SymphonyCMS.gitignore6
-rw-r--r--vendor/gitignore/TeX.gitignore180
-rw-r--r--vendor/gitignore/Terraform.gitignore3
-rw-r--r--vendor/gitignore/Textpattern.gitignore11
-rw-r--r--vendor/gitignore/TurboGears2.gitignore20
-rw-r--r--vendor/gitignore/Typo3.gitignore20
-rw-r--r--vendor/gitignore/Umbraco.gitignore19
-rw-r--r--vendor/gitignore/Unity.gitignore30
-rw-r--r--vendor/gitignore/UnrealEngine.gitignore62
-rw-r--r--vendor/gitignore/VVVV.gitignore6
-rw-r--r--vendor/gitignore/VisualStudio.gitignore252
-rw-r--r--vendor/gitignore/Waf.gitignore4
-rw-r--r--vendor/gitignore/WordPress.gitignore18
-rw-r--r--vendor/gitignore/Xojo.gitignore11
-rw-r--r--vendor/gitignore/Yeoman.gitignore6
-rw-r--r--vendor/gitignore/Yii.gitignore6
-rw-r--r--vendor/gitignore/ZendFramework.gitignore25
-rw-r--r--vendor/gitignore/Zephir.gitignore26
1198 files changed, 20290 insertions, 8262 deletions
diff --git a/.gitignore b/.gitignore
index 8f861d76a37..ce6a363fe35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,46 +4,46 @@
.bundle
.chef
.directory
-.envrc
-.gitlab_shell_secret
+/.envrc
+/.gitlab_shell_secret
.idea
-.rbenv-version
+/.rbenv-version
.rbx/
-.ruby-gemset
-.ruby-version
-.rvmrc
+/.ruby-gemset
+/.ruby-version
+/.rvmrc
.sass-cache/
-.secret
-.vagrant
-.byebug_history
-Vagrantfile
-backups/*
-config/aws.yml
-config/database.yml
-config/gitlab.yml
-config/gitlab_ci.yml
-config/initializers/rack_attack.rb
-config/initializers/smtp_settings.rb
-config/initializers/relative_url.rb
-config/resque.yml
-config/unicorn.rb
-config/secrets.yml
-config/sidekiq.yml
-coverage/*
-db/*.sqlite3
-db/*.sqlite3-journal
-db/data.yml
-doc/code/*
-dump.rdb
-log/*.log*
-nohup.out
-public/assets/
-public/uploads.*
-public/uploads/
-shared/artifacts/
-rails_best_practices_output.html
+/.secret
+/.vagrant
+/.byebug_history
+/Vagrantfile
+/backups/*
+/config/aws.yml
+/config/database.yml
+/config/gitlab.yml
+/config/gitlab_ci.yml
+/config/initializers/rack_attack.rb
+/config/initializers/smtp_settings.rb
+/config/initializers/relative_url.rb
+/config/resque.yml
+/config/unicorn.rb
+/config/secrets.yml
+/config/sidekiq.yml
+/coverage/*
+/db/*.sqlite3
+/db/*.sqlite3-journal
+/db/data.yml
+/doc/code/*
+/dump.rdb
+/log/*.log*
+/nohup.out
+/public/assets/
+/public/uploads.*
+/public/uploads/
+/shared/artifacts/
+/rails_best_practices_output.html
/tags
-tmp/
-vendor/bundle/*
-builds/*
-shared/*
+/tmp/*
+/vendor/bundle/*
+/builds/*
+/shared/*
diff --git a/.rubocop.yml b/.rubocop.yml
index 9f179efa3ce..84a8015b410 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,3 +1,5 @@
+require: rubocop-rspec
+
AllCops:
TargetRubyVersion: 2.1
# Cop names are not displayed in offense messages by default. Change behavior
@@ -21,6 +23,7 @@ AllCops:
- 'lib/email_validator.rb'
- 'lib/gitlab/upgrader.rb'
- 'lib/gitlab/seeder.rb'
+ - 'generator_templates/**/*'
##################### Style ##################################
@@ -56,7 +59,7 @@ Style/AndOr:
# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
- Enabled: false
+ Enabled: true
# Use only ascii symbols in comments.
Style/AsciiComments:
@@ -68,7 +71,7 @@ Style/AsciiIdentifiers:
# Checks for uses of Module#attr.
Style/Attr:
- Enabled: false
+ Enabled: true
# Avoid the use of BEGIN blocks.
Style/BeginBlock:
@@ -80,7 +83,7 @@ Style/BarePercentLiterals:
# Do not use block comments.
Style/BlockComments:
- Enabled: false
+ Enabled: true
# Put end statement of multiline block on its own line.
Style/BlockEndNewline:
@@ -121,7 +124,7 @@ Style/ClassCheck:
# Use self when defining module/class methods.
Style/ClassMethods:
- Enabled: false
+ Enabled: true
# Avoid the use of class variables.
Style/ClassVars:
@@ -151,7 +154,7 @@ Style/ConstantName:
# Use def with parentheses when there are arguments.
Style/DefWithParentheses:
- Enabled: false
+ Enabled: true
# Checks for use of deprecated Hash methods.
Style/DeprecatedHashMethods:
@@ -215,15 +218,15 @@ Style/EmptyLiteral:
# Avoid the use of END blocks.
Style/EndBlock:
- Enabled: false
+ Enabled: true
# Use Unix-style line endings.
Style/EndOfLine:
- Enabled: false
+ Enabled: true
# Favor the use of Fixnum#even? && Fixnum#odd?
Style/EvenOdd:
- Enabled: false
+ Enabled: true
# Do not use unnecessary spacing.
Style/ExtraSpacing:
@@ -231,15 +234,20 @@ Style/ExtraSpacing:
# Use snake_case for source file names.
Style/FileName:
- Enabled: false
+ Enabled: true
+
+# Checks for a line break before the first parameter in a multi-line method
+# parameter definition.
+Style/FirstMethodParameterLineBreak:
+ Enabled: true
# Checks for flip flops.
Style/FlipFlop:
- Enabled: false
+ Enabled: true
# Checks use of for or each in multiline loops.
Style/For:
- Enabled: false
+ Enabled: true
# Enforce the use of Kernel#sprintf, Kernel#format or String#%.
Style/FormatString:
@@ -247,7 +255,7 @@ Style/FormatString:
# Do not introduce global variables.
Style/GlobalVars:
- Enabled: false
+ Enabled: true
# Check for conditionals that can be replaced with guard clauses.
Style/GuardClause:
@@ -268,7 +276,7 @@ Style/IfUnlessModifier:
# Do not use if x; .... Use the ternary operator instead.
Style/IfWithSemicolon:
- Enabled: false
+ Enabled: true
# Checks that conditional statements do not have an identical line at the
# end of each branch, which can validly be moved out of the conditional.
@@ -276,9 +284,9 @@ Style/IdenticalConditionalBranches:
Enabled: false
# Checks the indentation of the first line of the right-hand-side of a
-# multi-line assignment.
+# multi-line assignment.
Style/IndentAssignment:
- Enabled: false
+ Enabled: true
# Keep indentation straight.
Style/IndentationConsistency:
@@ -298,7 +306,7 @@ Style/IndentHash:
# Use Kernel#loop for infinite loops.
Style/InfiniteLoop:
- Enabled: false
+ Enabled: true
# Use the new lambda literal syntax for single-line blocks.
Style/Lambda:
@@ -306,11 +314,11 @@ Style/Lambda:
# Use lambda.call(...) instead of lambda.(...).
Style/LambdaCall:
- Enabled: false
+ Enabled: true
# Comments should start with a space.
Style/LeadingCommentSpace:
- Enabled: false
+ Enabled: true
# Use \ instead of + or << to concatenate two string literals at line end.
Style/LineEndConcatenation:
@@ -322,16 +330,22 @@ Style/MethodCallParentheses:
# Checks if the method definitions have or don't have parentheses.
Style/MethodDefParentheses:
- Enabled: false
+ Enabled: true
# Use the configured style when naming methods.
Style/MethodName:
- Enabled: false
+ Enabled: true
# Checks for usage of `extend self` in modules.
Style/ModuleFunction:
Enabled: false
+# Checks that the closing brace in an array literal is either on the same line
+# as the last array element, or a new line.
+Style/MultilineArrayBraceLayout:
+ Enabled: false
+ EnforcedStyle: symmetrical
+
# Avoid multi-line chains of blocks.
Style/MultilineBlockChain:
Enabled: false
@@ -340,15 +354,32 @@ Style/MultilineBlockChain:
Style/MultilineBlockLayout:
Enabled: true
+# Checks that the closing brace in a hash literal is either on the same line as
+# the last hash element, or a new line.
+Style/MultilineHashBraceLayout:
+ Enabled: false
+ EnforcedStyle: symmetrical
+
# Do not use then for multi-line if/unless.
Style/MultilineIfThen:
+ Enabled: true
+
+# Checks that the closing brace in a method call is either on the same line as
+# the last method argument, or a new line.
+Style/MultilineMethodCallBraceLayout:
Enabled: false
+ EnforcedStyle: symmetrical
# Checks indentation of method calls with the dot operator that span more than
# one line.
Style/MultilineMethodCallIndentation:
Enabled: false
+# Checks that the closing brace in a method definition is symmetrical with
+# respect to the opening brace and the method parameters.
+Style/MultilineMethodDefinitionBraceLayout:
+ Enabled: false
+
# Checks indentation of binary operations that span more than one line.
Style/MultilineOperationIndentation:
Enabled: false
@@ -363,7 +394,7 @@ Style/MutableConstant:
# Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf:
- Enabled: false
+ Enabled: true
# Favor until over while for negative conditions.
Style/NegatedWhile:
@@ -371,7 +402,7 @@ Style/NegatedWhile:
# Avoid using nested modifiers.
Style/NestedModifier:
- Enabled: false
+ Enabled: true
# Parenthesize method calls which are nested inside the argument list of
# another parenthesized method call.
@@ -408,7 +439,7 @@ Style/OneLineConditional:
# When defining binary operators, name the argument other.
Style/OpMethod:
- Enabled: false
+ Enabled: true
# Check for simple usages of parallel assignment. It will only warn when
# the number of variables matches on both sides of the assignment.
@@ -455,10 +486,9 @@ Style/RedundantException:
Style/RedundantFreeze:
Enabled: false
-# TODO: Enable RedundantParentheses Cop.
# Checks for parentheses that seem not to serve any purpose.
Style/RedundantParentheses:
- Enabled: false
+ Enabled: true
# Don't use return where it's not required.
Style/RedundantReturn:
@@ -484,11 +514,12 @@ Style/SelfAssignment:
# Don't use semicolons to terminate expressions.
Style/Semicolon:
- Enabled: false
+ Enabled: true
# Checks for proper usage of fail and raise.
Style/SignalException:
- Enabled: false
+ EnforcedStyle: only_raise
+ Enabled: true
# Enforces the names of some block params.
Style/SingleLineBlockParams:
@@ -509,29 +540,28 @@ Style/SpaceAfterComma:
# Do not put a space between a method name and the opening parenthesis in a
# method definition.
Style/SpaceAfterMethodName:
- Enabled: false
+ Enabled: true
# Tracks redundant space after the ! operator.
Style/SpaceAfterNot:
- Enabled: false
+ Enabled: true
# Use spaces after semicolons.
Style/SpaceAfterSemicolon:
- Enabled: false
+ Enabled: true
# Checks that the equals signs in parameter default assignments have or don't
# have surrounding space depending on configuration.
Style/SpaceAroundEqualsInParameterDefault:
Enabled: false
-# TODO: Enable SpaceAroundKeyword Cop.
# Use a space around keywords if appropriate.
Style/SpaceAroundKeyword:
- Enabled: false
+ Enabled: true
# Use a single space around operators.
Style/SpaceAroundOperators:
- Enabled: false
+ Enabled: true
# Checks that the left block brace has or doesn't have space before it.
Style/SpaceBeforeBlockBraces:
@@ -539,11 +569,11 @@ Style/SpaceBeforeBlockBraces:
# No spaces before commas.
Style/SpaceBeforeComma:
- Enabled: false
+ Enabled: true
# Checks for missing space between code and a comment on the same line.
Style/SpaceBeforeComment:
- Enabled: false
+ Enabled: true
# Checks that exactly one space is used between a method name and the first
# argument for method calls without parentheses.
@@ -552,7 +582,7 @@ Style/SpaceBeforeFirstArg:
# No spaces before semicolons.
Style/SpaceBeforeSemicolon:
- Enabled: false
+ Enabled: true
# Checks that block braces have or don't have surrounding space.
# For blocks taking parameters, checks that the left brace has or doesn't
@@ -574,11 +604,12 @@ Style/SpaceInsideParens:
# No spaces inside range literals.
Style/SpaceInsideRangeLiteral:
- Enabled: false
+ Enabled: true
# Checks for padding/surrounding spaces inside string interpolation.
Style/SpaceInsideStringInterpolation:
- Enabled: false
+ EnforcedStyle: no_space
+ Enabled: true
# Avoid Perl-style global variables.
Style/SpecialGlobalVars:
@@ -586,7 +617,8 @@ Style/SpecialGlobalVars:
# Check for the usage of parentheses around stabby lambda arguments.
Style/StabbyLambdaParentheses:
- Enabled: false
+ EnforcedStyle: require_parentheses
+ Enabled: true
# Checks if uses of quotes match the configured preference.
Style/StringLiterals:
@@ -599,7 +631,9 @@ Style/StringLiteralsInInterpolation:
# Checks if configured preferred methods are used over non-preferred.
Style/StringMethods:
- Enabled: false
+ PreferredMethods:
+ intern: to_sym
+ Enabled: true
# Use %i or %I for arrays of symbols.
Style/SymbolArray:
@@ -657,23 +691,24 @@ Style/UnneededPercentQ:
# Don't interpolate global, instance and class variables directly in strings.
Style/VariableInterpolation:
- Enabled: false
+ Enabled: true
# Use the configured style when naming variables.
Style/VariableName:
- Enabled: false
+ EnforcedStyle: snake_case
+ Enabled: true
# Use when x then ... for one-line cases.
Style/WhenThen:
- Enabled: false
+ Enabled: true
# Checks for redundant do after while or until.
Style/WhileUntilDo:
- Enabled: false
+ Enabled: true
# Favor modifier while/until usage when you have a single-line body.
Style/WhileUntilModifier:
- Enabled: false
+ Enabled: true
# Use %w or %W for arrays of words.
Style/WordArray:
@@ -749,28 +784,28 @@ Lint/AssignmentInCondition:
# Align block ends correctly.
Lint/BlockAlignment:
- Enabled: false
+ Enabled: true
# Default values in optional keyword arguments and optional ordinal arguments
# should not refer back to the name of the argument.
Lint/CircularArgumentReference:
- Enabled: false
+ Enabled: true
# Checks for condition placed in a confusing position relative to the keyword.
Lint/ConditionPosition:
- Enabled: false
+ Enabled: true
# Check for debugger calls.
Lint/Debugger:
- Enabled: false
+ Enabled: true
# Align ends corresponding to defs correctly.
Lint/DefEndAlignment:
- Enabled: false
+ Enabled: true
# Check for deprecated class method calls.
Lint/DeprecatedClassMethods:
- Enabled: false
+ Enabled: true
# Check for duplicate method definitions.
Lint/DuplicateMethods:
@@ -782,15 +817,15 @@ Lint/DuplicatedKey:
# Check for immutable argument given to each_with_object.
Lint/EachWithObjectArgument:
- Enabled: false
+ Enabled: true
# Check for odd code arrangement in an else block.
Lint/ElseLayout:
- Enabled: false
+ Enabled: true
# Checks for empty ensure block.
Lint/EmptyEnsure:
- Enabled: false
+ Enabled: true
# Checks for empty string interpolation.
Lint/EmptyInterpolation:
@@ -798,37 +833,36 @@ Lint/EmptyInterpolation:
# Align ends correctly.
Lint/EndAlignment:
- Enabled: false
+ Enabled: true
# END blocks should not be placed inside method definitions.
Lint/EndInMethod:
- Enabled: false
+ Enabled: true
# Do not use return in an ensure block.
Lint/EnsureReturn:
- Enabled: false
+ Enabled: true
# The use of eval represents a serious security risk.
Lint/Eval:
- Enabled: false
+ Enabled: true
# Catches floating-point literals too large or small for Ruby to represent.
Lint/FloatOutOfRange:
- Enabled: false
+ Enabled: true
# The number of parameters to format/sprint must match the fields.
Lint/FormatParameterMismatch:
- Enabled: false
+ Enabled: true
# Don't suppress exception.
Lint/HandleExceptions:
Enabled: false
-# TODO: Enable ImplicitStringConcatenation Cop.
# Checks for adjacent string literals on the same line, which could better be
# represented as a single string literal.
Lint/ImplicitStringConcatenation:
- Enabled: false
+ Enabled: true
# TODO: Enable IneffectiveAccessModifier Cop.
# Checks for attempts to use `private` or `protected` to set the visibility
@@ -839,15 +873,15 @@ Lint/IneffectiveAccessModifier:
# Checks for invalid character literals with a non-escaped whitespace
# character.
Lint/InvalidCharacterLiteral:
- Enabled: false
+ Enabled: true
# Checks of literals used in conditions.
Lint/LiteralInCondition:
- Enabled: false
+ Enabled: true
# Checks for literals used in interpolation.
Lint/LiteralInInterpolation:
- Enabled: false
+ Enabled: true
# Use Kernel#loop with break rather than begin/end/until or begin/end/while
# for post-loop tests.
@@ -856,11 +890,11 @@ Lint/Loop:
# Do not use nested method definitions.
Lint/NestedMethodDefinition:
- Enabled: false
+ Enabled: true
# Do not omit the accumulator when calling `next` in a `reduce`/`inject` block.
Lint/NextWithoutAccumulator:
- Enabled: false
+ Enabled: true
# Checks for method calls with a space before the opening parenthesis.
Lint/ParenthesesAsGroupedExpression:
@@ -869,11 +903,11 @@ Lint/ParenthesesAsGroupedExpression:
# Checks for `rand(1)` calls. Such calls always return `0` and most likely
# a mistake.
Lint/RandOne:
- Enabled: false
+ Enabled: true
# Use parentheses in the method call to avoid confusion about precedence.
Lint/RequireParentheses:
- Enabled: false
+ Enabled: true
# Avoid rescuing the Exception class.
Lint/RescueException:
@@ -908,7 +942,7 @@ Lint/UnusedMethodArgument:
# Unreachable code.
Lint/UnreachableCode:
- Enabled: false
+ Enabled: true
# Checks for useless access modifiers.
Lint/UselessAccessModifier:
@@ -920,33 +954,31 @@ Lint/UselessAssignment:
# Checks for comparison of something with itself.
Lint/UselessComparison:
- Enabled: false
+ Enabled: true
# Checks for useless `else` in `begin..end` without `rescue`.
Lint/UselessElseWithoutRescue:
- Enabled: false
+ Enabled: true
# Checks for useless setter call to a local variable.
Lint/UselessSetterCall:
- Enabled: false
+ Enabled: true
# Possible use of operator/literal/variable in void context.
Lint/Void:
- Enabled: false
+ Enabled: true
##################### Performance ############################
-# TODO: Enable Casecmp Cop.
# Use `casecmp` rather than `downcase ==`.
Performance/Casecmp:
- Enabled: false
+ Enabled: true
-# TODO: Enable DoubleStartEndWith Cop.
# Use `str.{start,end}_with?(x, ..., y, ...)` instead of
# `str.{start,end}_with?(x, ...) || str.{start,end}_with?(y, ...)`.
Performance/DoubleStartEndWith:
- Enabled: false
+ Enabled: true
# TODO: Enable EndWith Cop.
# Use `end_with?` instead of a regex match anchored to the end of a string.
@@ -957,10 +989,9 @@ Performance/EndWith:
Performance/LstripRstrip:
Enabled: true
-# TODO: Enable RangeInclude Cop.
# Use `Range#cover?` instead of `Range#include?`.
Performance/RangeInclude:
- Enabled: false
+ Enabled: true
# TODO: Enable RedundantBlockCall Cop.
# Use `yield` instead of `block.call`.
@@ -980,26 +1011,24 @@ Performance/RedundantMerge:
MaxKeyValuePairs: 2
Enabled: false
-# TODO: Enable RedundantSortBy Cop.
# Use `sort` instead of `sort_by { |x| x }`.
Performance/RedundantSortBy:
- Enabled: false
+ Enabled: true
-# TODO: Enable StartWith Cop.
# Use `start_with?` instead of a regex match anchored to the beginning of a
# string.
Performance/StartWith:
- Enabled: false
+ Enabled: true
+
# Use `tr` instead of `gsub` when you are replacing the same number of
# characters. Use `delete` instead of `gsub` when you are deleting
# characters.
Performance/StringReplacement:
- Enabled: false
+ Enabled: true
-# TODO: Enable TimesMap Cop.
# Checks for `.times.map` calls.
Performance/TimesMap:
- Enabled: false
+ Enabled: true
##################### Rails ##################################
@@ -1024,11 +1053,11 @@ Rails/Delegate:
# Prefer `find_by` over `where.first`.
Rails/FindBy:
- Enabled: false
+ Enabled: true
# Prefer `all.find_each` over `all.find`.
Rails/FindEach:
- Enabled: false
+ Enabled: true
# Prefer has_many :through to has_and_belongs_to_many.
Rails/HasAndBelongsToMany:
@@ -1040,7 +1069,7 @@ Rails/Output:
# Checks for incorrect grammar when using methods like `3.day.ago`.
Rails/PluralizationGrammar:
- Enabled: false
+ Enabled: true
# Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
Rails/ReadWriteAttribute:
@@ -1048,7 +1077,7 @@ Rails/ReadWriteAttribute:
# Checks the arguments of ActiveRecord scopes.
Rails/ScopeArgs:
- Enabled: false
+ Enabled: true
# Checks the correct usage of time zone aware methods.
# http://danilenko.org/2012/7/6/rails_timezones
@@ -1058,3 +1087,65 @@ Rails/TimeZone:
# Use validates :attribute, hash of validations.
Rails/Validation:
Enabled: false
+
+##################### RSpec ##################################
+
+# Check that instances are not being stubbed globally.
+RSpec/AnyInstance:
+ Enabled: false
+
+# Check that the first argument to the top level describe is the tested class or
+# module.
+RSpec/DescribeClass:
+ Enabled: false
+
+# Use `described_class` for tested class / module.
+RSpec/DescribeMethod:
+ Enabled: false
+
+# Checks that the second argument to top level describe is the tested method
+# name.
+RSpec/DescribedClass:
+ Enabled: false
+
+# Checks for long example.
+RSpec/ExampleLength:
+ Enabled: false
+ Max: 5
+
+# Do not use should when describing your tests.
+RSpec/ExampleWording:
+ Enabled: false
+ CustomTransform:
+ be: is
+ have: has
+ not: does not
+ IgnoredWords: []
+
+# Checks the file and folder naming of the spec file.
+RSpec/FilePath:
+ Enabled: false
+ CustomTransform:
+ RuboCop: rubocop
+ RSpec: rspec
+
+# Checks if there are focused specs.
+RSpec/Focus:
+ Enabled: true
+
+# Checks for the usage of instance variables.
+RSpec/InstanceVariable:
+ Enabled: false
+
+# Checks for multiple top-level describes.
+RSpec/MultipleDescribes:
+ Enabled: false
+
+# Enforces the usage of the same method on all negative message expectations.
+RSpec/NotToNot:
+ EnforcedStyle: not_to
+ Enabled: true
+
+# Prefer using verifying doubles over normal doubles.
+RSpec/VerifiedDoubles:
+ Enabled: false
diff --git a/app/views/projects/notes/_commit_discussion.html.haml b/.vagrant_enabled
index e69de29bb2d..e69de29bb2d 100644
--- a/app/views/projects/notes/_commit_discussion.html.haml
+++ b/.vagrant_enabled
diff --git a/CHANGELOG b/CHANGELOG
index 822fa4be565..ec026b8f398 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,47 +1,180 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.8.0 (unreleased)
+v 8.9.0 (unreleased)
+ - Allow enabling wiki page events from Webhook management UI
+ - Make EmailsOnPushWorker use Sidekiq mailers queue
+ - Fix wiki page events' webhook to point to the wiki repository
+ - Fix issue todo not remove when leave project !4150 (Long Nguyen)
+ - Allow forking projects with restricted visibility level
+ - Improve note validation to prevent errors when creating invalid note via API
+ - Reduce number of fog gem dependencies
+ - Remove project notification settings associated with deleted projects
+ - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
+ - Redesign navigation for project pages
+ - Fix groups API to list only user's accessible projects
+ - Redesign account and email confirmation emails
+ - Use gitlab-shell v3.0.0
+ - Add DB index on users.state
+ - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
+ - Changed the Slack build message to use the singular duration if necessary (Aran Koning)
+ - Fix issues filter when ordering by milestone
+ - Todos will display target state if issuable target is 'Closed' or 'Merged'
+ - Fix bug when sorting issues by milestone due date and filtering by two or more labels
+ - Link to blank group icon doesn't throw a 404 anymore
+ - Remove 'main language' feature
+ - Pipelines can be canceled only when there are running builds
+ - Use downcased path to container repository as this is expected path by Docker
+ - Projects pending deletion will render a 404 page
+ - Measure queue duration between gitlab-workhorse and Rails
+ - Make authentication service for Container Registry to be compatible with < Docker 1.11
+ - Add Application Setting to configure Container Registry token expire delay (default 5min)
+ - Cache assigned issue and merge request counts in sidebar nav
+ - Cache project build count in sidebar nav
+ - Reduce number of queries needed to render issue labels in the sidebar
+ - Improve error handling importing projects
+ - Put project Files and Commits tabs under Code tab
+
+v 8.8.3
+ - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
+ - Fixed JS error when trying to remove discussion form. !4303
+ - Fixed issue with button color when no CI enabled. !4287
+ - Fixed potential issue with 2 CI status polling events happening. !3869
+ - Improve design of Pipeline view. !4230
+ - Fix gitlab importer failing to import new projects due to missing credentials. !4301
+ - Fix import URL migration not rescuing with the correct Error. !4321
+ - Fix health check access token changing due to old application settings being used. !4332
+ - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363
+ - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364
+ - Pass the "Remember me" value to the 2FA token form. !4369
+ - Fix incorrect links on pipeline page when merge request created from fork. !4376
+ - Use downcased path to container repository as this is expected path by Docker. !4420
+ - Fix wiki project clone address error (chujinjin). !4429
+ - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392
+ - Fix missing number on generated ordered list element. !4437
+ - Prevent disclosure of notes on confidential issues in search results.
+
+v 8.8.2
+ - Added remove due date button. !4209
+ - Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242
+ - Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245
+ - Fix table UI on CI builds page. !4249
+ - Fix backups if registry is disabled. !4263
+ - Fixed issue with merge button color. !4211
+ - Fixed issue with enter key selecting wrong option in dropdown. !4210
+ - When creating a .gitignore file a dropdown with templates will be provided. !4075
+ - Fix concurrent request when updating build log in browser. !4183
+
+v 8.8.1
+ - Add documentation for the "Health Check" feature
+ - Allow anonymous users to access a public project's pipelines !4233
+ - Fix MySQL compatibility in zero downtime migrations helpers
+ - Fix the CI login to Container Registry (the gitlab-ci-token user)
+
+v 8.8.0
+ - Implement GFM references for milestones (Alejandro Rodríguez)
+ - Snippets tab under user profile. !4001 (Long Nguyen)
+ - Fix error when using link to uploads in global snippets
+ - Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
+ - Use a case-insensitive comparison in sanitizing URI schemes
+ - Toggle sign-up confirmation emails in application settings
+ - Make it possible to prevent tagged runner from picking untagged jobs
+ - Added `InlineDiffFilter` to the markdown parser. (Adam Butler)
+ - Added inline diff styling for `change_title` system notes. (Adam Butler)
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Escape HTML in commit titles in system note messages
+ - Improve design of Pipeline View
+ - Fix scope used when accessing container registry
+ - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios
- Improve multiple branch push performance by memoizing permission checking
- Log to application.log when an admin starts and stops impersonating a user
+ - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi)
- 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
+ - Added /health_check endpoint for checking service status
+ - Make 'upcoming' filter for milestones work better across projects
- 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
+ - Fix API leak of notes of unauthorized issues, snippets and merge requests
- 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
+ - Speed up push emails with multiple recipients by only generating the email once
- Updated search UI
+ - Added authentication service for Container Registry
- Display informative message when new milestone is created
+ - Sanitize milestones and labels titles
+ - Support multi-line tag messages. !3833 (Calin Seciu)
+ - Force users to reset their password after an admin changes it
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- Backport GitHub Enterprise import support from EE
- Create tags using Rugged for performance reasons. !3745
+ - Allow guests to set notification level in projects
+ - API: Expose Issue#user_notes_count. !3126 (Anton Popov)
+ - Don't show forks button when user can't view forks
+ - Fix atom feed links and rendering
- 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)
+ - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
- Added multiple colors for labels in dropdowns when dups happen.
+ - Show commits in the same order as `git log`
- 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)
+ - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi)
+ - Fix adding a todo for private group members (Ahmad Sherif)
+ - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
+ - Total method execution timings are no longer tracked
+ - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga)
+ - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
+ - Hide left sidebar on phone screens to give more space for content
+ - Redesign navigation for profile and group pages
+ - Add counter metrics for rails cache
+ - Import pull requests from GitHub where the source or target branches were removed
+ - All Grape API helpers are now instrumented
+ - Improve Issue formatting for the Slack Service (Jeroen van Baarsen)
+ - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
+ - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs)
+ - When creating a .gitignore file a dropdown with templates will be provided
+
+v 8.7.7
+ - Fix import by `Any Git URL` broken if the URL contains a space
+
+v 8.7.6
+ - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
+ - Fix import from GitLab.com to a private instance failure. !4181
+ - Fix external imports not finding the import data. !4106
+ - Fix notification delay when changing status of an issue
+
+v 8.7.5
+ - Fix relative links in wiki pages. !4050
+ - Fix always showing build notification message when switching between merge requests !4086
+ - Fix an issue when filtering merge requests with more than one label. !3886
+ - Fix short note for the default scope on build page (Takuya Noguchi)
v 8.7.4
- - Fix always showing build notification message when switching between merge requests
+ - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
+ - Fix setting trusted proxies !3970
+ - Fix BitBucket importer bug when throwing exceptions !3941
+ - Use sign out path only if not empty !3989
+ - Running rake gitlab:db:drop_tables now drops tables with cascade !4020
+ - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100
+ - Use a case-insensitive comparison in sanitizing URI schemes
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
+ - Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea)
v 8.7.2
- The "New Branch" button is now loaded asynchronously
@@ -850,7 +983,7 @@ v 8.1.3
- Use issue editor as cross reference comment author when issue is edited with a new mention
- Add Facebook authentication
-v 8.1.2
+v 8.1.1
- Fix cloning Wiki repositories via HTTP (Stan Hu)
- Add migration to remove satellites directory
- Fix specific runners visibility
@@ -1475,20 +1608,17 @@ v 7.10.0
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
- Fix merge request comments on files with multiple commits
- Fix Resource Owner Password Authentication Flow
-
-v 7.9.4
- - Security: Fix project import URL regex to prevent arbitary local repos from being imported
- - Fixed issue where only 25 commits would load in file listings
- - Fix LDAP identities after config update
-
-v 7.9.3
- - Contains no changes
- Add icons to Add dropdown items.
- Allow admin to create public deploy keys that are accessible to any project.
- Warn when gitlab-shell version doesn't match requirement.
- Skip email confirmation when set by admin or via LDAP.
- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
+v 7.9.4
+ - Security: Fix project import URL regex to prevent arbitary local repos from being imported
+ - Fixed issue where only 25 commits would load in file listings
+ - Fix LDAP identities after config update
+
v 7.9.3
- Contains no changes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9fe4cf7b0f6..a15f8c4fec7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -311,13 +311,11 @@ request is as follows:
1. Create a feature branch
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
-1. If you are changing the README, some documentation or other things which
- have no effect on the tests, add `[ci skip]` somewhere in the commit message
- and make sure to read the [documentation styleguide][doc-styleguide]
+1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash]
1. Push the commit(s) to your fork
-1. Submit a merge request (MR) to the master branch
+1. Submit a merge request (MR) to the `master` branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
used to achieve it, see the [merge request description format]
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 37c2961c243..4a36342fcab 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.7.2
+3.0.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 39e898a4f95..0a1ffad4b4d 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.7.1
+0.7.4
diff --git a/Gemfile b/Gemfile
index 1137ef4d72b..d9429de786f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,9 +18,8 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
-gem 'devise', '~> 3.5.4'
+gem 'devise', '~> 4.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'
@@ -36,15 +35,16 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
+gem 'jwt'
# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
-gem 'devise-two-factor', '~> 2.0.0'
+gem 'devise-two-factor', '~> 3.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
-gem 'attr_encrypted', '~> 1.3.4'
+gem 'attr_encrypted', '~> 3.0.0'
# Browser detection
gem "browser", '~> 1.0.0'
@@ -72,7 +72,7 @@ gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
-gem "kaminari", "~> 0.16.3"
+gem "kaminari", "~> 0.17.0"
# HAML
gem "haml-rails", '~> 0.9.0'
@@ -83,8 +83,14 @@ gem "carrierwave", '~> 0.10.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
+# for backups
+gem 'fog-aws', '~> 0.9'
+gem 'fog-core', '~> 1.40'
+gem 'fog-local', '~> 0.3'
+gem 'fog-google', '~> 0.3'
+gem 'fog-openstack', '~> 0.1'
+
# for aws storage
-gem "fog", "~> 1.36.0"
gem "unf", '~> 0.1.4'
# Authorization
@@ -120,7 +126,7 @@ group :unicorn do
end
# State machine
-gem "state_machines-activerecord", '~> 0.3.0'
+gem "state_machines-activerecord", '~> 0.4.0'
# Run events after state machine commits
gem 'after_commit_queue'
@@ -177,9 +183,6 @@ gem 'ruby-fogbugz', '~> 0.2.1'
# d3
gem 'd3_rails', '~> 3.5.0'
-#cal-heatmap
-gem 'cal-heatmap-rails', '~> 3.6.0'
-
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
@@ -197,7 +200,7 @@ gem 'licensee', '~> 8.0.0'
gem "rack-attack", '~> 4.3.1'
# Ace editor
-gem 'ace-rails-ap', '~> 2.0.1'
+gem 'ace-rails-ap', '~> 4.0.2'
# Keyboard shortcuts
gem 'mousetrap-rails', '~> 1.4.6'
@@ -218,13 +221,13 @@ gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
-gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
+gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
@@ -242,7 +245,6 @@ group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
- gem "annotate", "~> 2.7.0"
gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
@@ -293,9 +295,10 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
- gem 'rubocop', '~> 0.38.0', require: false
+ gem 'rubocop', '~> 0.40.0', require: false
+ gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
- gem 'coveralls', '~> 0.8.2', require: false
+ gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false
gem 'flay', require: false
@@ -325,8 +328,7 @@ gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8'
## CI
-gem 'activerecord-deprecated_finders', '~> 1.0.3'
-gem 'activerecord-session_store', '~> 0.1.0'
+gem 'activerecord-session_store', '~> 1.0.0'
gem "nested_form", '~> 0.3.2'
# OAuth
@@ -334,3 +336,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
+
+# Health check
+gem 'health_check', '~> 1.5.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index fe083c2b566..8ae25269e65 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,9 +1,8 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (2.3.2)
RedCloth (4.2.9)
- ace-rails-ap (2.0.1)
+ ace-rails-ap (4.0.2)
actionmailer (4.2.6)
actionpack (= 4.2.6)
actionview (= 4.2.6)
@@ -33,11 +32,12 @@ GEM
activemodel (= 4.2.6)
activesupport (= 4.2.6)
arel (~> 6.0)
- activerecord-deprecated_finders (1.0.4)
- activerecord-session_store (0.1.2)
- actionpack (>= 4.0.0, < 5)
- activerecord (>= 4.0.0, < 5)
- railties (>= 4.0.0, < 5)
+ activerecord-session_store (1.0.0)
+ actionpack (>= 4.0, < 5.1)
+ activerecord (>= 4.0, < 5.1)
+ multi_json (~> 1.11, >= 1.11.2)
+ rack (>= 1.5.2, < 3)
+ railties (>= 4.0, < 5.1)
activesupport (4.2.6)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
@@ -51,9 +51,6 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
- annotate (2.7.0)
- activerecord (>= 3.2, < 6.0)
- rake (~> 10.4)
arel (6.0.3)
asana (0.4.0)
faraday (~> 0.9)
@@ -62,8 +59,8 @@ GEM
oauth2 (~> 1.0)
asciidoctor (1.5.3)
ast (2.2.0)
- attr_encrypted (1.3.4)
- encryptor (>= 1.3.0)
+ attr_encrypted (3.0.1)
+ encryptor (~> 3.0.0)
attr_required (1.0.0)
autoprefixer-rails (6.2.3)
execjs
@@ -74,7 +71,8 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
- bcrypt (3.1.10)
+ base32 (0.3.2)
+ bcrypt (3.1.11)
benchmark-ips (2.3.0)
better_errors (1.0.1)
coderay (>= 1.0.0)
@@ -103,7 +101,6 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
- cal-heatmap-rails (3.6.0)
capybara (2.6.2)
addressable
mime-types (>= 1.16)
@@ -157,21 +154,18 @@ GEM
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
- devise (3.5.4)
+ devise (4.1.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
- railties (>= 3.2.6, < 5)
+ railties (>= 4.1.0, < 5.1)
responders
- thread_safe (~> 0.1)
warden (~> 1.2.3)
- devise-async (0.9.0)
- devise (~> 3.2)
- devise-two-factor (2.0.1)
+ devise-two-factor (3.0.0)
activesupport
- attr_encrypted (~> 1.3.2)
- devise (~> 3.5.0)
+ attr_encrypted (>= 1.3, < 4, != 2)
+ devise (~> 4.0)
railties
- rotp (~> 2)
+ rotp (~> 2.0)
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
@@ -183,12 +177,12 @@ GEM
email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
- encryptor (1.3.0)
+ encryptor (3.0.0)
equalizer (0.0.11)
erubis (2.7.0)
escape_utils (1.1.1)
eventmachine (1.0.8)
- excon (0.45.4)
+ excon (0.49.0)
execjs (2.6.0)
expression_parser (0.9.0)
factory_girl (4.5.0)
@@ -205,8 +199,6 @@ GEM
multi_json
ffaker (2.0.0)
ffi (1.9.10)
- fission (0.5.0)
- CFPropertyList (~> 2.2)
flay (2.6.1)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
@@ -216,109 +208,28 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
- fog (1.36.0)
- fog-aliyun (>= 0.1.0)
- fog-atmos
- fog-aws (>= 0.6.0)
- fog-brightbox (~> 0.4)
- fog-core (~> 1.32)
- fog-dynect (~> 0.0.2)
- fog-ecloud (~> 0.1)
- fog-google (<= 0.1.0)
- fog-json
- fog-local
- fog-powerdns (>= 0.1.1)
- fog-profitbricks
- fog-radosgw (>= 0.0.2)
- fog-riakcs
- fog-sakuracloud (>= 0.0.4)
- fog-serverlove
- fog-softlayer
- fog-storm_on_demand
- fog-terremark
- fog-vmfusion
- fog-voxel
- fog-xenserver
- fog-xml (~> 0.1.1)
- ipaddress (~> 0.5)
- nokogiri (~> 1.5, >= 1.5.11)
- fog-aliyun (0.1.0)
- fog-core (~> 1.27)
- fog-json (~> 1.0)
- ipaddress (~> 0.8)
- xml-simple (~> 1.1)
- fog-atmos (0.1.0)
- fog-core
- fog-xml
- fog-aws (0.8.1)
+ fog-aws (0.9.2)
fog-core (~> 1.27)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-brightbox (0.10.1)
- fog-core (~> 1.22)
- fog-json
- inflecto (~> 0.0.2)
- fog-core (1.35.0)
+ fog-core (1.40.0)
builder
- excon (~> 0.45)
+ excon (~> 0.49)
formatador (~> 0.2)
- fog-dynect (0.0.2)
- fog-core
- fog-json
- fog-xml
- fog-ecloud (0.3.0)
- fog-core
- fog-xml
- fog-google (0.1.0)
+ fog-google (0.3.2)
fog-core
fog-json
fog-xml
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
- fog-local (0.2.1)
- fog-core (~> 1.27)
- fog-powerdns (0.1.1)
+ fog-local (0.3.0)
fog-core (~> 1.27)
- fog-json (~> 1.0)
- fog-xml (~> 0.1)
- fog-profitbricks (0.0.5)
- fog-core
- fog-xml
- nokogiri
- fog-radosgw (0.0.5)
- fog-core (>= 1.21.0)
- fog-json
- fog-xml (>= 0.0.1)
- fog-riakcs (0.1.0)
- fog-core
- fog-json
- fog-xml
- fog-sakuracloud (1.7.5)
- fog-core
- fog-json
- fog-serverlove (0.1.2)
- fog-core
- fog-json
- fog-softlayer (1.0.3)
- fog-core
- fog-json
- fog-storm_on_demand (0.1.1)
- fog-core
- fog-json
- fog-terremark (0.1.0)
- fog-core
- fog-xml
- fog-vmfusion (0.1.0)
- fission
- fog-core
- fog-voxel (0.1.0)
- fog-core
- fog-xml
- fog-xenserver (0.2.2)
- fog-core
- fog-xml
+ fog-openstack (0.1.6)
+ fog-core (>= 1.39)
+ fog-json (>= 1.0)
+ ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
@@ -405,6 +316,8 @@ GEM
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3)
+ health_check (1.5.1)
+ rails (>= 2.3.0)
highline (1.7.8)
hipchat (1.5.2)
httparty
@@ -425,18 +338,15 @@ GEM
httpclient (2.7.0.1)
i18n (0.7.0)
ice_nine (0.11.1)
- inflecto (0.0.2)
influxdb (0.2.3)
cause
json
- ipaddress (0.8.2)
+ ipaddress (0.8.3)
jquery-atwho-rails (1.3.2)
jquery-rails (4.1.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- jquery-scrollto-rails (1.4.3)
- railties (> 3.1, < 5.0)
jquery-turbolinks (2.1.0)
railties (>= 3.1.0)
turbolinks
@@ -444,7 +354,7 @@ GEM
railties (>= 3.2.16)
json (1.8.3)
jwt (1.5.2)
- kaminari (0.16.3)
+ kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.10.0)
@@ -552,7 +462,7 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
- parser (2.3.0.6)
+ parser (2.3.1.0)
ast (~> 2.2)
pg (0.18.4)
poltergeist (1.9.0)
@@ -658,7 +568,7 @@ GEM
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
rinku (1.7.3)
- rotp (2.1.1)
+ rotp (2.1.2)
rouge (1.10.1)
rqrcode (0.7.0)
chunky_png
@@ -687,15 +597,17 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.4.1)
- rubocop (0.38.0)
- parser (>= 2.3.0.6, < 3.0)
+ rubocop (0.40.0)
+ parser (>= 2.3.1.0, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
+ rubocop-rspec (1.5.0)
+ rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-progressbar (1.7.5)
+ ruby-progressbar (1.8.1)
ruby-saml (1.1.2)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
@@ -789,11 +701,11 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
state_machines (0.4.0)
- state_machines-activemodel (0.3.0)
- activemodel (~> 4.1)
+ state_machines-activemodel (0.4.0)
+ activemodel (>= 4.1, < 5.1)
state_machines (>= 0.4.0)
- state_machines-activerecord (0.3.0)
- activerecord (~> 4.1)
+ state_machines-activerecord (0.4.0)
+ activerecord (>= 4.1, < 5.1)
state_machines-activemodel (>= 0.3.0)
stringex (2.5.2)
systemu (2.6.5)
@@ -842,7 +754,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
- unicode-display_width (1.0.2)
+ unicode-display_width (1.0.5)
unicorn (4.9.0)
kgio (~> 2.6)
rack
@@ -859,7 +771,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
- warden (1.2.4)
+ warden (1.2.6)
rack (>= 1.0)
web-console (2.3.0)
activemodel (>= 4.0)
@@ -876,7 +788,6 @@ GEM
builder
expression_parser
rinku
- xml-simple (1.1.5)
xpath (2.0.0)
nokogiri (~> 1.3)
@@ -885,20 +796,19 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.2.9)
- ace-rails-ap (~> 2.0.1)
- activerecord-deprecated_finders (~> 1.0.3)
- activerecord-session_store (~> 0.1.0)
+ ace-rails-ap (~> 4.0.2)
+ activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
- annotate (~> 2.7.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
- attr_encrypted (~> 1.3.4)
+ attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
+ base32 (~> 0.3.0)
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
@@ -908,7 +818,6 @@ DEPENDENCIES
bullet
bundler-audit
byebug
- cal-heatmap-rails (~> 3.6.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
@@ -921,9 +830,8 @@ DEPENDENCIES
d3_rails (~> 3.5.0)
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)
+ devise (~> 4.0)
+ devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1)
@@ -933,7 +841,11 @@ DEPENDENCIES
ffaker (~> 2.0.0)
flay
flog
- fog (~> 1.36.0)
+ fog-aws (~> 0.9)
+ fog-core (~> 1.40)
+ fog-google (~> 0.3)
+ fog-local (~> 0.3)
+ fog-openstack (~> 0.1)
font-awesome-rails (~> 4.2)
foreman
fuubar (~> 2.0.0)
@@ -951,16 +863,17 @@ DEPENDENCIES
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0)
+ health_check (~> 1.5.1)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty (~> 0.13.3)
influxdb (~> 0.2)
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0)
- jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 5.0.0)
- kaminari (~> 0.16.3)
+ jwt
+ kaminari (~> 0.17.0)
letter_opener_web (~> 1.3.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3)
@@ -1016,7 +929,8 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.4.0)
rspec-retry
- rubocop (~> 0.38.0)
+ rubocop (~> 0.40.0)
+ rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
@@ -1041,7 +955,7 @@ DEPENDENCIES
spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0)
- state_machines-activerecord (~> 0.3.0)
+ state_machines-activerecord (~> 0.4.0)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
@@ -1061,4 +975,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.12.1
+ 1.12.4
diff --git a/README.md b/README.md
index 5e41665a6ba..fee93d5f9c3 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,7 @@
# GitLab
[![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)
## Canonical source
@@ -35,11 +33,11 @@ There are two editions of GitLab:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
-- [Subscriptions](https://about.gitlab.com/subscription/)
+- [Subscriptions](https://about.gitlab.com/pricing/)
- [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
-- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
+- [GitLab Enterprise Edition](https://about.gitlab.com/features/#enterprise) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Requirements
diff --git a/VERSION b/VERSION
index d5a967c3933..6c07f656285 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.8.0-pre
+8.9.0-pre
diff --git a/app/assets/images/ci/arch.jpg b/app/assets/images/ci/arch.jpg
deleted file mode 100644
index 0e05674e840..00000000000
--- a/app/assets/images/ci/arch.jpg
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/ci/favicon.ico b/app/assets/images/ci/favicon.ico
deleted file mode 100644
index 9663d4d00b9..00000000000
--- a/app/assets/images/ci/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/ci/loader.gif b/app/assets/images/ci/loader.gif
deleted file mode 100644
index 2fcb8f2da0d..00000000000
--- a/app/assets/images/ci/loader.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/ci/no_avatar.png b/app/assets/images/ci/no_avatar.png
deleted file mode 100644
index 752d26adba7..00000000000
--- a/app/assets/images/ci/no_avatar.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/ci/rails.png b/app/assets/images/ci/rails.png
deleted file mode 100644
index d5edc04e65f..00000000000
--- a/app/assets/images/ci/rails.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/ci/service_sample.png b/app/assets/images/ci/service_sample.png
deleted file mode 100644
index 65d29e3fd89..00000000000
--- a/app/assets/images/ci/service_sample.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_header_logo.png b/app/assets/images/mailers/gitlab_header_logo.png
new file mode 100644
index 00000000000..35ca1860887
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_header_logo.png
Binary files differ
diff --git a/app/assets/images/mailers/gitlab_tanuki_2x.png b/app/assets/images/mailers/gitlab_tanuki_2x.png
new file mode 100644
index 00000000000..551dd6ce2ce
--- /dev/null
+++ b/app/assets/images/mailers/gitlab_tanuki_2x.png
Binary files differ
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index dd1bbb37551..3f61ea1eaf4 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -1,14 +1,15 @@
@Api =
- groups_path: "/api/:version/groups.json"
- group_path: "/api/:version/groups/:id.json"
- namespaces_path: "/api/:version/namespaces.json"
- group_projects_path: "/api/:version/groups/:id/projects.json"
- projects_path: "/api/:version/projects.json"
- labels_path: "/api/:version/projects/:id/labels"
- license_path: "/api/:version/licenses/:key"
+ groupsPath: "/api/:version/groups.json"
+ groupPath: "/api/:version/groups/:id.json"
+ namespacesPath: "/api/:version/namespaces.json"
+ groupProjectsPath: "/api/:version/groups/:id/projects.json"
+ projectsPath: "/api/:version/projects.json"
+ labelsPath: "/api/:version/projects/:id/labels"
+ licensePath: "/api/:version/licenses/:key"
+ gitignorePath: "/api/:version/gitignores/:key"
group: (group_id, callback) ->
- url = Api.buildUrl(Api.group_path)
+ url = Api.buildUrl(Api.groupPath)
url = url.replace(':id', group_id)
$.ajax(
@@ -22,7 +23,7 @@
# Return groups list. Filtered by query
# Only active groups retrieved
groups: (query, skip_ldap, callback) ->
- url = Api.buildUrl(Api.groups_path)
+ url = Api.buildUrl(Api.groupsPath)
$.ajax(
url: url
@@ -36,7 +37,7 @@
# Return namespaces list. Filtered by query
namespaces: (query, callback) ->
- url = Api.buildUrl(Api.namespaces_path)
+ url = Api.buildUrl(Api.namespacesPath)
$.ajax(
url: url
@@ -50,7 +51,7 @@
# Return projects list. Filtered by query
projects: (query, order, callback) ->
- url = Api.buildUrl(Api.projects_path)
+ url = Api.buildUrl(Api.projectsPath)
$.ajax(
url: url
@@ -64,7 +65,7 @@
callback(projects)
newLabel: (project_id, data, callback) ->
- url = Api.buildUrl(Api.labels_path)
+ url = Api.buildUrl(Api.labelsPath)
url = url.replace(':id', project_id)
data.private_token = gon.api_token
@@ -80,7 +81,7 @@
# Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) ->
- url = Api.buildUrl(Api.group_projects_path)
+ url = Api.buildUrl(Api.groupProjectsPath)
url = url.replace(':id', group_id)
$.ajax(
@@ -95,7 +96,7 @@
# Return text for a specific license
licenseText: (key, data, callback) ->
- url = Api.buildUrl(Api.license_path).replace(':key', key)
+ url = Api.buildUrl(Api.licensePath).replace(':key', key)
$.ajax(
url: url
@@ -103,6 +104,12 @@
).done (license) ->
callback(license)
+ gitignoreText: (key, callback) ->
+ url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
+
+ $.get url, (gitignore) ->
+ callback(gitignore)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 5bac8eef1cb..18c1aa0d4e2 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -18,8 +18,6 @@
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.turbolinks
-#= require d3
-#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap/affix
@@ -52,7 +50,13 @@
#= require shortcuts_network
#= require jquery.nicescroll
#= require date.format
-#= require_tree .
+#= require_directory ./behaviors
+#= require_directory ./blob
+#= require_directory ./ci
+#= require_directory ./commit
+#= require_directory ./extensions
+#= require_directory ./lib
+#= require_directory .
#= require fuzzaldrin-plus
#= require cropper
@@ -204,6 +208,7 @@ $ ->
$('.header-content .title').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
+ $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
@@ -245,38 +250,6 @@ $ ->
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
- $(document)
- .off 'click', '.js-sidebar-toggle'
- .on 'click', '.js-sidebar-toggle', (e, triggered) ->
- e.preventDefault()
- $this = $(this)
- $thisIcon = $this.find 'i'
- $allGutterToggleIcons = $('.js-sidebar-toggle i')
- if $thisIcon.hasClass('fa-angle-double-right')
- $allGutterToggleIcons
- .removeClass('fa-angle-double-right')
- .addClass('fa-angle-double-left')
- $('aside.right-sidebar')
- .removeClass('right-sidebar-expanded')
- .addClass('right-sidebar-collapsed')
- $('.page-with-sidebar')
- .removeClass('right-sidebar-expanded')
- .addClass('right-sidebar-collapsed')
- else
- $allGutterToggleIcons
- .removeClass('fa-angle-double-left')
- .addClass('fa-angle-double-right')
- $('aside.right-sidebar')
- .removeClass('right-sidebar-collapsed')
- .addClass('right-sidebar-expanded')
- $('.page-with-sidebar')
- .removeClass('right-sidebar-collapsed')
- .addClass('right-sidebar-expanded')
- if not triggered
- $.cookie("collapsed_gutter",
- $('.right-sidebar')
- .hasClass('right-sidebar-collapsed'), { path: '/' })
-
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee b/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
new file mode 100644
index 00000000000..cc8a497d081
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
@@ -0,0 +1,58 @@
+class @BlobGitignoreSelector
+ constructor: (opts) ->
+ {
+ @dropdown
+ @editor
+ @$wrapper = @dropdown.closest('.gitignore-selector')
+ @$filenameInput = $('#file_name')
+ @data = @dropdown.data('filenames')
+ } = opts
+
+ @dropdown.glDropdown(
+ data: @data,
+ filterable: true,
+ selectable: true,
+ search:
+ fields: ['name']
+ clicked: @onClick
+ text: (gitignore) ->
+ gitignore.name
+ )
+
+ @toggleGitignoreSelector()
+ @bindEvents()
+
+ bindEvents: ->
+ @$filenameInput
+ .on 'keyup blur', (e) =>
+ @toggleGitignoreSelector()
+
+ toggleGitignoreSelector: ->
+ filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
+ @$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
+
+ onClick: (item, el, e) =>
+ e.preventDefault()
+ @requestIgnoreFile(item.name)
+
+ requestIgnoreFile: (name) ->
+ Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
+
+ requestIgnoreFileSuccess: (gitignore) ->
+ @editor.setValue(gitignore.content, 1)
+ @editor.focus()
+
+class @BlobGitignoreSelectors
+ constructor: (opts) ->
+ {
+ @$dropdowns = $('.js-gitignore-selector')
+ @editor
+ } = opts
+
+ @$dropdowns.each (i, dropdown) =>
+ $dropdown = $(dropdown)
+
+ new BlobGitignoreSelector(
+ dropdown: $dropdown,
+ editor: @editor
+ )
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index eea9aa972ee..79141e768b8 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -13,6 +13,7 @@ class @EditBlob
@initModePanesAndLinks()
new BlobLicenseSelector(@editor)
+ new BlobGitignoreSelectors(editor: @editor)
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee
deleted file mode 100644
index d80e0e716ce..00000000000
--- a/app/assets/javascripts/calendar.js.coffee
+++ /dev/null
@@ -1,34 +0,0 @@
-class @Calendar
- constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
- cal = new CalHeatMap()
- cal.init
- itemName: ["contribution"]
- data: timestamps
- start: new Date(starting_year, starting_month)
- domainLabelFormat: "%b"
- id: "cal-heatmap"
- domain: "month"
- subDomain: "day"
- range: 12
- tooltip: true
- label:
- position: "top"
- legend: [
- 0
- 10
- 20
- 30
- ]
- legendCellPadding: 3
- cellSize: $('.user-calendar').width() / 73
- onClick: (date, count) ->
- formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
- $.ajax
- url: calendar_activities_path
- data:
- date: formated_date
- cache: false
- dataType: "html"
- success: (data) ->
- $(".user-calendar-activities").html data
-
diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee
index 05aa0f366bb..ca24c1d759f 100644
--- a/app/assets/javascripts/ci/application.js.coffee
+++ b/app/assets/javascripts/ci/application.js.coffee
@@ -1,34 +1,6 @@
-# This is a manifest file that'll be compiled into application.js, which will include all the files
-# listed below.
-#
-# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-#
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-# GO AFTER THE REQUIRES BELOW.
-#
#= require pager
#= require jquery_nested_form
#= require_tree .
-#
-$(document).on 'click', '.edit-runner-link', (event) ->
- event.preventDefault()
-
- descr = $(this).closest('.runner-description').first()
- descr.addClass('hide')
- form = descr.next('.runner-description-form')
- descrInput = form.find('input.description')
- originalValue = descrInput.val()
- form.removeClass('hide')
- form.find('.cancel').on 'click', (event) ->
- event.preventDefault()
-
- form.addClass('hide')
- descrInput.val(originalValue)
- descr.removeClass('hide')
$(document).on 'click', '.assign-all-runner', ->
$(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee
index 7afe8bf79e2..98d05e41273 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/ci/build.coffee
@@ -1,9 +1,12 @@
class CiBuild
@interval: null
+ @state: null
- constructor: (build_url, build_status) ->
+ constructor: (build_url, build_status, build_state) ->
clearInterval(CiBuild.interval)
+ @state = build_state
+
@initScrollButtonAffix()
if build_status == "running" || build_status == "pending"
@@ -25,15 +28,22 @@ class CiBuild
#
CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url
+ last_state = @state
$.ajax
- url: build_url
+ url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
dataType: "json"
- success: (build) =>
- if build.status == "running"
- $('#build-trace code').html build.trace_html
- $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
+ success: (log) =>
+ return unless last_state is @state
+
+ if log.state and log.status is "running"
+ @state = log.state
+ if log.append
+ $('.fa-refresh').before log.html
+ else
+ $('#build-trace code').html log.html
+ $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
@checkAutoscroll()
- else if build.status != build_status
+ else if log.status isnt build_status
Turbolinks.visit build_url
, 4000
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index f91aa3c5ad7..a3185f87640 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -16,7 +16,6 @@ class Dispatcher
shortcut_handler = null
switch page
when 'projects:issues:index'
- Issues.init()
Issuable.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
@@ -119,7 +118,7 @@ class Dispatcher
new UsersSelect()
when 'projects'
new NamespaceSelect()
- when 'dashboard'
+ when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
new Profile()
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
index a4304786cbb..3cc70185178 100644
--- a/app/assets/javascripts/due_date_select.js.coffee
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -11,6 +11,7 @@ class @DueDateSelect
$block = $dropdown.closest('.block')
$selectbox = $dropdown.closest('.selectbox')
$value = $block.find('.value')
+ $valueContent = $block.find('.value-content')
$sidebarValue = $('.js-due-date-sidebar-value', $block)
fieldName = $dropdown.data('field-name')
@@ -23,11 +24,15 @@ class @DueDateSelect
$value.removeAttr('style')
)
- addDueDate = ->
+ addDueDate = (isDropdown) ->
# Create the post date
value = $("input[name='#{fieldName}']").val()
- date = new Date value.replace(new RegExp('-', 'g'), ',')
- mediumDate = $.datepicker.formatDate 'M d, yy', date
+
+ if value isnt ''
+ date = new Date value.replace(new RegExp('-', 'g'), ',')
+ mediumDate = $.datepicker.formatDate 'M d, yy', date
+ else
+ mediumDate = 'None'
data = {}
data[abilityName] = {}
@@ -39,23 +44,35 @@ class @DueDateSelect
data: data
beforeSend: ->
$loading.fadeIn()
- $dropdown.trigger('loading.gl.dropdown')
- $selectbox.hide()
+ if isDropdown
+ $dropdown.trigger('loading.gl.dropdown')
+ $selectbox.hide()
$value.removeAttr('style')
- $value.html(mediumDate)
+ $valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
+
+ if value isnt ''
+ $('.js-remove-due-date-holder').removeClass 'hidden'
+ else
+ $('.js-remove-due-date-holder').addClass 'hidden'
).done (data) ->
- $dropdown.trigger('loaded.gl.dropdown')
- $dropdown.dropdown('toggle')
+ if isDropdown
+ $dropdown.trigger('loaded.gl.dropdown')
+ $dropdown.dropdown('toggle')
$loading.fadeOut()
+ $block.on 'click', '.js-remove-due-date', (e) ->
+ e.preventDefault()
+ $("input[name='#{fieldName}']").val ''
+ addDueDate(false)
+
$datePicker.datepicker(
dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='#{fieldName}']").val()
altField: "input[name='#{fieldName}']"
onSelect: ->
- addDueDate()
+ addDueDate(true)
)
$(document)
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 61e3f811e73..41dba342107 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -18,6 +18,10 @@ GitLab.GfmAutoComplete =
Issues:
template: '<li><small>${id}</small> ${title}</li>'
+ # Milestones
+ Milestones:
+ template: '<li>${title}</li>'
+
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
@@ -82,6 +86,19 @@ GitLab.GfmAutoComplete =
search: "#{i.iid} #{i.title}"
@input.atwho
+ at: '%'
+ alias: 'milestones'
+ searchKey: 'search'
+ displayTpl: @Milestones.template
+ insertTpl: '${atwho-at}"${title}"'
+ callbacks:
+ beforeSave: (milestones) ->
+ $.map milestones, (m) ->
+ id: m.iid
+ title: sanitize(m.title)
+ search: "#{m.title}"
+
+ @input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
@@ -105,6 +122,8 @@ GitLab.GfmAutoComplete =
@input.atwho 'load', '@', data.members
# load issues
@input.atwho 'load', 'issues', data.issues
+ # load milestones
+ @input.atwho 'load', 'milestones', data.milestones
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 1d1bfeb2e77..b3f1dc969b8 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -60,9 +60,36 @@ class GitLabDropdownFilter
results = data
if search_text isnt ''
- results = fuzzaldrinPlus.filter(data, search_text,
- key: @options.keys
- )
+ # When data is an array of objects therefore [object Array] e.g.
+ # [
+ # { prop: 'foo' },
+ # { prop: 'baz' }
+ # ]
+ if _.isArray(data)
+ results = fuzzaldrinPlus.filter(data, search_text,
+ key: @options.keys
+ )
+ else
+ # If data is grouped therefore an [object Object]. e.g.
+ # {
+ # groupName1: [
+ # { prop: 'foo' },
+ # { prop: 'baz' }
+ # ],
+ # groupName2: [
+ # { prop: 'abc' },
+ # { prop: 'def' }
+ # ]
+ # }
+ if gl.utils.isObject data
+ results = {}
+ for key, group of data
+ tmp = fuzzaldrinPlus.filter(group, search_text,
+ key: @options.keys
+ )
+
+ if tmp.length
+ results[key] = tmp.map (item) -> item
@options.callback results
else
@@ -141,8 +168,9 @@ class GitLabDropdown
searchFields = if @options.search then @options.search.fields else [];
if @options.data
- # If data is an array
- if _.isArray @options.data
+ # If we provided data
+ # data could be an array of objects or a group of arrays
+ if _.isObject(@options.data) and not _.isFunction(@options.data)
@fullData = @options.data
@parseData @options.data
else
@@ -230,19 +258,33 @@ class GitLabDropdown
parseData: (data) ->
@renderedData = data
- # Render each row
- html = $.map data, (obj) =>
- return @renderItem(obj)
-
if @options.filterable and data.length is 0
# render no matching results
html = [@noResults()]
+ else
+ # Handle array groups
+ if gl.utils.isObject data
+ html = []
+ for name, groupData of data
+ # Add header for each group
+ html.push(@renderItem(header: name, name))
+
+ @renderData(groupData, name)
+ .map (item) ->
+ html.push item
+ else
+ # Render each row
+ html = @renderData(data)
# Render the full menu
full_html = @renderMenu(html.join(""))
@appendMenu(full_html)
+ renderData: (data, group = false) ->
+ data.map (obj, index) =>
+ return @renderItem(obj, group, index)
+
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
@@ -299,11 +341,10 @@ class GitLabDropdown
selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content"
-
$(selector, @dropdown).html html
# Render the row
- renderItem: (data) ->
+ renderItem: (data, group = false, index = false) ->
html = ""
# Divider
@@ -346,8 +387,13 @@ class GitLabDropdown
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
+ if group
+ groupAttrs = "data-group='#{group}' data-index='#{index}'"
+ else
+ groupAttrs = ''
+
html = "<li>
- <a href='#{url}' class='#{cssClass}'>
+ <a href='#{url}' #{groupAttrs} class='#{cssClass}'>
#{text}
</a>
</li>"
@@ -377,9 +423,15 @@ class GitLabDropdown
rowClicked: (el) ->
fieldName = @options.fieldName
- selectedIndex = el.parent().index()
if @renderedData
- selectedObject = @renderedData[selectedIndex]
+ groupName = el.data('group')
+ if groupName
+ selectedIndex = el.data('index')
+ selectedObject = @renderedData[groupName][selectedIndex]
+ else
+ selectedIndex = el.closest('li').index()
+ selectedObject = @renderedData[selectedIndex]
+
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
@@ -460,7 +512,7 @@ class GitLabDropdown
return false
if currentKeyCode is 13
- @selectRowAtIndex currentIndex
+ @selectRowAtIndex if currentIndex < 0 then 0 else currentIndex
removeArrayKeyEvent: ->
$('body').off 'keydown'
diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/application.js.coffee
new file mode 100644
index 00000000000..e0f681acf0b
--- /dev/null
+++ b/app/assets/javascripts/graphs/application.js.coffee
@@ -0,0 +1,7 @@
+# This is a manifest file that'll be compiled into including all the files listed below.
+# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+# be included in the compiled file accessible from http://example.com/assets/application.js
+# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+# the compiled file.
+#
+#= require_tree .
diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph.js.coffee
index f36c71fd25e..f36c71fd25e 100644
--- a/app/assets/javascripts/stat_graph.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph.js.coffee
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
index 3be14cb43dd..1d9fae7cf79 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
@@ -1,5 +1,4 @@
#= require d3
-#= require stat_graph_contributors_util
class @ContributorsStatGraph
init: (log) ->
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
index b7a0e073766..584d281a510 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
@@ -1,6 +1,4 @@
#= require d3
-#= require jquery
-#= require underscore
class @ContributorsGraph
MARGIN:
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
index 31617c88b4a..31617c88b4a 100644
--- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
index afffed63ac5..6504e481102 100644
--- a/app/assets/javascripts/issuable.js.coffee
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -1,7 +1,11 @@
+issuable_created = false
@Issuable =
init: ->
- Issuable.initTemplates()
- Issuable.initSearch()
+ unless issuable_created
+ issuable_created = true
+ Issuable.initTemplates()
+ Issuable.initSearch()
+ Issuable.initChecks()
initTemplates: ->
Issuable.labelRow = _.template(
@@ -19,7 +23,16 @@
.on 'keyup', ->
clearTimeout(@timer)
@timer = setTimeout( ->
- Issuable.filterResults $('#issue_search_form')
+ $search = $('#issue_search')
+ $form = $('.js-filter-form')
+ $input = $("input[name='#{$search.attr('name')}']", $form)
+
+ if $input.length is 0
+ $form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
+ else
+ $input.val $search.val()
+
+ Issuable.filterResults $form
, 500)
toggleLabelFilters: ->
@@ -59,15 +72,22 @@
dataType: "json"
reload: ->
- if Issues.created
- Issues.initChecks()
+ if Issuable.created
+ Issuable.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
+ initChecks: ->
+ $('.check_all_issues').on 'click', ->
+ $('.selected_issue').prop('checked', @checked)
+ Issuable.checkChanged()
+
+ $('.selected_issue').on 'change', Issuable.checkChanged
+
updateStateFilters: ->
- stateFilters = $('.issues-state-filters')
+ stateFilters = $('.issues-state-filters, .dropdown-menu-sort')
newParams = {}
- paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
+ paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
@@ -82,3 +102,17 @@
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
+
+ checkChanged: ->
+ checked_issues = $('.selected_issue:checked')
+ if checked_issues.length > 0
+ ids = $.map checked_issues, (value) ->
+ $(value).data('id')
+
+ $('#update_issues_ids').val ids
+ $('.issues-other-filters').hide()
+ $('.issues_bulk_update').show()
+ else
+ $('#update_issues_ids').val []
+ $('.issues_bulk_update').hide()
+ $('.issues-other-filters').show()
diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
index 7a788f761b7..898506fde32 100644
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ b/app/assets/javascripts/issuable_form.js.coffee
@@ -19,6 +19,16 @@ class @IssuableForm
@form.on "click", ".btn-cancel", @resetAutosave
@initWip()
+ @initMoveDropdown()
+
+ $issuableDueDate = $('#issuable-due-date')
+
+ if $issuableDueDate.length
+ $('.datepicker').datepicker(
+ dateFormat: 'yy-mm-dd',
+ onSelect: (dateText, inst) ->
+ $issuableDueDate.val dateText
+ ).datepicker 'setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())
initAutosave: ->
new Autosave @titleField, [
@@ -80,3 +90,19 @@ class @IssuableForm
addWip: ->
@titleField.val "WIP: #{@titleField.val()}"
+
+ initMoveDropdown: ->
+ $moveDropdown = $('.js-move-dropdown')
+
+ if $moveDropdown.length
+ $('.js-move-dropdown').select2
+ ajax:
+ url: $moveDropdown.data('projects-url')
+ results: (data) ->
+ return {
+ results: data
+ }
+ formatResult: (project) ->
+ project.name_with_namespace
+ formatSelection: (project) ->
+ project.name_with_namespace
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
deleted file mode 100644
index 3330e6c68ad..00000000000
--- a/app/assets/javascripts/issues.js.coffee
+++ /dev/null
@@ -1,38 +0,0 @@
-@Issues =
- init: ->
- Issues.created = true
- Issues.initChecks()
-
- $("body").on "ajax:success", ".close_issue, .reopen_issue", ->
- t = $(this)
- totalIssues = undefined
- reopen = t.hasClass("reopen_issue")
- $(".issue_counter").each ->
- issue = $(this)
- totalIssues = parseInt($(this).html(), 10)
- if reopen and issue.closest(".main_menu").length
- $(this).html totalIssues + 1
- else
- $(this).html totalIssues - 1
-
- initChecks: ->
- $(".check_all_issues").click ->
- $(".selected_issue").prop("checked", @checked)
- Issues.checkChanged()
-
- $(".selected_issue").bind "change", Issues.checkChanged
-
- checkChanged: ->
- checked_issues = $(".selected_issue:checked")
- if checked_issues.length > 0
- ids = []
- $.each checked_issues, (index, value) ->
- ids.push $(value).attr("data-id")
-
- $("#update_issues_ids").val ids
- $(".issues-other-filters").hide()
- $(".issues_bulk_update").show()
- else
- $("#update_issues_ids").val []
- $(".issues_bulk_update").hide()
- $(".issues-other-filters").show()
diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee
new file mode 100644
index 00000000000..6adac6dac97
--- /dev/null
+++ b/app/assets/javascripts/layout_nav.js.coffee
@@ -0,0 +1,14 @@
+class @LayoutNav
+ $ ->
+ $('.fade-left').addClass('end-scroll')
+ $('.scrolling-tabs').on 'scroll', (event) ->
+ $this = $(this)
+ $el = $(event.target)
+ currentPosition = $this.scrollLeft()
+ size = bp.getBreakpointSize()
+ controlBtnWidth = $('.controls').width()
+ maxPosition = $this.get(0).scrollWidth - $this.parent().width()
+ maxPosition += controlBtnWidth if size isnt 'xs' and $('.nav-control').length
+
+ $el.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
+ $el.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
diff --git a/app/assets/javascripts/lib/type_utility.js.coffee b/app/assets/javascripts/lib/type_utility.js.coffee
new file mode 100644
index 00000000000..957f0d86b36
--- /dev/null
+++ b/app/assets/javascripts/lib/type_utility.js.coffee
@@ -0,0 +1,9 @@
+((w) ->
+
+ w.gl ?= {}
+ w.gl.utils ?= {}
+
+ w.gl.utils.isObject = (obj) ->
+ obj? and (obj.constructor is Object)
+
+) window
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee
index 6a00932c028..e8085e1c2e4 100644
--- a/app/assets/javascripts/lib/url_utility.js.coffee
+++ b/app/assets/javascripts/lib/url_utility.js.coffee
@@ -26,10 +26,19 @@
newUrl = decodeURIComponent(url)
for paramName, paramValue of params
pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
- if url.search(pattern) >= 0
+ if not paramValue?
+ newUrl = newUrl.replace pattern, ''
+ else if url.search(pattern) isnt -1
newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
else
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
+
+ # Remove a trailing ampersand
+ lastChar = newUrl[newUrl.length - 1]
+
+ if lastChar is '&'
+ newUrl = newUrl.slice 0, -1
+
newUrl
# removes parameter query string from url. returns the modified url
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 372732d0aac..49a4727205a 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -75,6 +75,9 @@ class @MergeRequestTabs
@loadDiff($target.attr('href'))
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView()
+
+ navBarHeight = $('.navbar-gitlab').outerHeight()
+ $.scrollTo(".merge-request-details .merge-request-tabs", offset: -navBarHeight)
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index f58647988a2..779f536d9f0 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -10,6 +10,7 @@ class @MergeRequestWidget
$('#modal_merge_info').modal(show: false)
@firstCICheck = true
@readyForCICheck = false
+ @cancel = false
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
@@ -21,10 +22,16 @@ class @MergeRequestWidget
clearEventListeners: ->
$(document).off 'page:change.merge_request'
+ cancelPolling: ->
+ @cancel = true
+
addEventListeners: ->
+ allowedPages = ['show', 'commits', 'builds', 'changes']
$(document).on 'page:change.merge_request', =>
- if $('body').data('page') isnt 'projects:merge_requests:show'
+ page = $('body').data('page').split(':').last()
+ if allowedPages.indexOf(page) < 0
clearInterval @fetchBuildStatusInterval
+ @cancelPolling()
@clearEventListeners()
mergeInProgress: (deleteSourceBranch = false)->
@@ -67,6 +74,7 @@ class @MergeRequestWidget
$('.ci-widget-fetching').show()
$.getJSON @opts.ci_status_url, (data) =>
+ return if @cancel
@readyForCICheck = true
if data.status is ''
@@ -106,6 +114,7 @@ class @MergeRequestWidget
@firstCICheck = false
showCIStatus: (state) ->
+ return if not state?
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
@@ -113,7 +122,7 @@ class @MergeRequestWidget
switch state
when "failed", "canceled", "not_found"
@setMergeButtonClass('btn-danger')
- when "running", "pending"
+ when "running"
@setMergeButtonClass('btn-warning')
when "success"
@setMergeButtonClass('btn-create')
@@ -126,6 +135,6 @@ class @MergeRequestWidget
$('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) ->
- $('.accept_merge_request')
+ $('.js-merge-button,.accept-action .dropdown-toggle')
.removeClass('btn-danger btn-warning btn-create')
.addClass(css_class)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index efb3e8e2198..f8151963fa7 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -114,9 +114,9 @@ class @Notes
@refresh()
, @pollingInterval
- refresh: ->
+ refresh: =>
return if @refreshing is true
- refreshing = true
+ @refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
@@ -134,8 +134,8 @@ class @Notes
@renderDiscussionNote(note)
else
@renderNote(note)
- always: =>
- @refreshing = false
+ .always () =>
+ @refreshing = false
###
Increase @pollingInterval up to 120 seconds on every function call,
@@ -285,6 +285,7 @@ class @Notes
form.addClass "js-main-target-form"
form.find("#note_line_code").remove()
+ form.find("#note_type").remove()
###
General note form setup.
@@ -328,7 +329,7 @@ class @Notes
@renderDiscussionNote(note)
# cleanup after successfully creating a diff/discussion note
- @removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
+ @removeDiscussionNoteForm($(xhr.target))
###
Called in response to the edit note form being submitted
@@ -472,6 +473,7 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
+ form.find("#note_type").val dataHolder.data("noteType")
form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode")
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
index 2d084b76cfe..c9cb0f4bb32 100644
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -10,6 +10,40 @@ class @Sidebar
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
+
+ $(document)
+ .off 'click', '.js-sidebar-toggle'
+ .on 'click', '.js-sidebar-toggle', (e, triggered) ->
+ e.preventDefault()
+ $this = $(this)
+ $thisIcon = $this.find 'i'
+ $allGutterToggleIcons = $('.js-sidebar-toggle i')
+ if $thisIcon.hasClass('fa-angle-double-right')
+ $allGutterToggleIcons
+ .removeClass('fa-angle-double-right')
+ .addClass('fa-angle-double-left')
+ $('aside.right-sidebar')
+ .removeClass('right-sidebar-expanded')
+ .addClass('right-sidebar-collapsed')
+ $('.page-with-sidebar')
+ .removeClass('right-sidebar-expanded')
+ .addClass('right-sidebar-collapsed')
+ else
+ $allGutterToggleIcons
+ .removeClass('fa-angle-double-left')
+ .addClass('fa-angle-double-right')
+ $('aside.right-sidebar')
+ .removeClass('right-sidebar-collapsed')
+ .addClass('right-sidebar-expanded')
+ $('.page-with-sidebar')
+ .removeClass('right-sidebar-collapsed')
+ .addClass('right-sidebar-expanded')
+ if not triggered
+ $.cookie("collapsed_gutter",
+ $('.right-sidebar')
+ .hasClass('right-sidebar-collapsed'), { path: '/' })
+
+
sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
@@ -76,7 +110,7 @@ class @Sidebar
@triggerOpenSidebar() if not @isOpen()
if action is 'hide'
- @triggerOpenSidebar() is @isOpen()
+ @triggerOpenSidebar() if @isOpen()
isOpen: ->
@sidebar.is('.right-sidebar-expanded')
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index 6a7b4ad1db7..2122e80f57a 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -20,8 +20,7 @@ class @SearchAutocomplete
@dropdown = @wrap.find('.dropdown')
@dropdownContent = @dropdown.find('.dropdown-content')
- @locationBadgeEl = @getElement('.search-location-badge')
- @locationText = @getElement('.location-text')
+ @locationBadgeEl = @getElement('.location-badge')
@scopeInputEl = @getElement('#scope')
@searchInput = @getElement('.search-input')
@projectInputEl = @getElement('#search_project_id')
@@ -133,7 +132,7 @@ class @SearchAutocomplete
scope: @scopeInputEl.val()
# Location badge
- _location: @locationText.text()
+ _location: @locationBadgeEl.text()
}
bindEvents: ->
@@ -143,12 +142,14 @@ class @SearchAutocomplete
@searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus
@clearInput.on 'click', @onClearInputClick
+ @locationBadgeEl.on 'click', =>
+ @searchInput.focus()
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
- if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
+ if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
@onSearchInputBlur()
enableAutocomplete: ->
@@ -221,10 +222,8 @@ class @SearchAutocomplete
category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else ''
- html = "<span class='location-badge'>
- <i class='location-text'>#{category}#{value}</i>
- </span>"
- @locationBadgeEl.html(html)
+ badgeText = "#{category}#{value}"
+ @locationBadgeEl.text(badgeText).show()
@wrap.addClass('has-location-badge')
restoreOriginalState: ->
@@ -233,9 +232,8 @@ class @SearchAutocomplete
for input in inputs
@getElement("##{input}").val(@originalState[input])
-
if @originalState._location is ''
- @locationBadgeEl.empty()
+ @locationBadgeEl.hide()
else
@addLocationBadge(
value: @originalState._location
@@ -244,7 +242,7 @@ class @SearchAutocomplete
@dropdown.removeClass 'open'
badgePresent: ->
- @locationBadgeEl.children().length
+ @locationBadgeEl.length
resetSearchState: ->
inputs = Object.keys @originalState
@@ -257,7 +255,7 @@ class @SearchAutocomplete
@getElement("##{input}").val('')
removeLocationBadge: ->
- @locationBadgeEl.empty()
+ @locationBadgeEl.hide()
# Reset state
@resetSearchState()
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
index 4a05bdccdb3..cca2b8a1fcc 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
@@ -3,10 +3,10 @@
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
- Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-activity'))
- Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-issues'))
- Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-merge_requests'))
- Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-projects'))
+ Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'))
+ Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'))
+ Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'))
+ Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'))
@findAndFollowLink: (selector) ->
link = $(selector).attr('href')
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index ad9b3c1c6bf..ccb42ab2168 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -6,6 +6,10 @@ class @ShortcutsIssuable extends ShortcutsNavigation
super()
Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
+ Mousetrap.bind('r', =>
+ @replyWithSelectedText()
+ return false
+ )
Mousetrap.bind('j', =>
@prevIssue()
return false
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index 860d4f438d0..ea4ac52da31 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -12,7 +12,7 @@ toggleSidebar = ->
niceScrollBars.updateScrollBar();
), 300
-$(document).on("click", '.toggle-nav-collapse', (e) ->
+$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault()
toggleSidebar()
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
index c2aeffe2381..70614396a4e 100644
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ b/app/assets/javascripts/user_tabs.js.coffee
@@ -26,6 +26,10 @@
# Personal projects
# </a>
# </li>
+# <li class="snippets-tab">
+# <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
+# </a>
+# </li>
# </ul>
#
# <div class="tab-content">
@@ -41,6 +45,9 @@
# <div class="tab-pane" id="projects">
# Projects content
# </div>
+# <div class="tab-pane" id="snippets">
+# Snippets content
+# </div>
# </div>
#
# <div class="loading-status">
@@ -100,7 +107,7 @@ class @UserTabs
if action is 'activity'
@loadActivities(source)
- if action in ['groups', 'contributed', 'projects']
+ if action in ['groups', 'contributed', 'projects', 'snippets']
@loadTab(source, action)
loadTab: (source, action) ->
diff --git a/app/assets/javascripts/users/application.js.coffee b/app/assets/javascripts/users/application.js.coffee
new file mode 100644
index 00000000000..647ffbf5f45
--- /dev/null
+++ b/app/assets/javascripts/users/application.js.coffee
@@ -0,0 +1,8 @@
+# This is a manifest file that'll be compiled into including all the files listed below.
+# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+# be included in the compiled file accessible from http://example.com/assets/application.js
+# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+# the compiled file.
+#
+#= require d3
+#= require_tree .
diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee
new file mode 100644
index 00000000000..26a26061539
--- /dev/null
+++ b/app/assets/javascripts/users/calendar.js.coffee
@@ -0,0 +1,198 @@
+class @Calendar
+ constructor: (timestamps, @calendar_activities_path) ->
+ @currentSelectedDate = ''
+ @daySpace = 1
+ @daySize = 15
+ @daySizeWithSpace = @daySize + (@daySpace * 2)
+ @monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ @months = []
+ @highestValue = 0
+
+ # Get the highest value from the timestampes
+ _.each timestamps, (count) =>
+ if count > @highestValue
+ @highestValue = count
+
+ # Loop through the timestamps to create a group of objects
+ # The group of objects will be grouped based on the day of the week they are
+ @timestampsTmp = []
+ i = 0
+ group = 0
+ _.each timestamps, (count, date) =>
+ newDate = new Date parseInt(date) * 1000
+ day = newDate.getDay()
+
+ # Create a new group array if this is the first day of the week
+ # or if is first object
+ if (day is 0 and i isnt 0) or i is 0
+ @timestampsTmp.push []
+ group++
+
+ innerArray = @timestampsTmp[group-1]
+
+ # Push to the inner array the values that will be used to render map
+ innerArray.push
+ count: count
+ date: newDate
+ day: day
+
+ i++
+
+ # Init color functions
+ @color = @initColor()
+ @colorKey = @initColorKey()
+
+ # Init the svg element
+ @renderSvg(group)
+ @renderDays()
+ @renderMonths()
+ @renderDayTitles()
+ @renderKey()
+
+ @initTooltips()
+
+ renderSvg: (group) ->
+ @svg = d3.select '.js-contrib-calendar'
+ .append 'svg'
+ .attr 'width', (group + 1) * @daySizeWithSpace
+ .attr 'height', 167
+ .attr 'class', 'contrib-calendar'
+
+ renderDays: ->
+ @svg.selectAll 'g'
+ .data @timestampsTmp
+ .enter()
+ .append 'g'
+ .attr 'transform', (group, i) =>
+ _.each group, (stamp, a) =>
+ if a is 0 and stamp.day is 0
+ month = stamp.date.getMonth()
+ x = (@daySizeWithSpace * i + 1) + @daySizeWithSpace
+ lastMonth = _.last(@months)
+ if lastMonth?
+ lastMonthX = lastMonth.x
+
+ if !lastMonth?
+ @months.push
+ month: month
+ x: x
+ else if month isnt lastMonth.month and x - @daySizeWithSpace isnt lastMonthX
+ @months.push
+ month: month
+ x: x
+
+ "translate(#{(@daySizeWithSpace * i + 1) + @daySizeWithSpace}, 18)"
+ .selectAll 'rect'
+ .data (stamp) ->
+ stamp
+ .enter()
+ .append 'rect'
+ .attr 'x', '0'
+ .attr 'y', (stamp, i) =>
+ (@daySizeWithSpace * stamp.day)
+ .attr 'width', @daySize
+ .attr 'height', @daySize
+ .attr 'title', (stamp) =>
+ contribText = 'No contributions'
+
+ if stamp.count > 0
+ contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}"
+
+ date = dateFormat(stamp.date, 'mmm d, yyyy')
+
+ "#{contribText}<br />#{date}"
+ .attr 'class', 'user-contrib-cell js-tooltip'
+ .attr 'fill', (stamp) =>
+ if stamp.count isnt 0
+ @color(stamp.count)
+ else
+ '#ededed'
+ .attr 'data-container', 'body'
+ .on 'click', @clickDay
+
+ renderDayTitles: ->
+ days = [{
+ text: 'M'
+ y: 29 + (@daySizeWithSpace * 1)
+ }, {
+ text: 'W'
+ y: 29 + (@daySizeWithSpace * 3)
+ }, {
+ text: 'F'
+ y: 29 + (@daySizeWithSpace * 5)
+ }]
+ @svg.append 'g'
+ .selectAll 'text'
+ .data days
+ .enter()
+ .append 'text'
+ .attr 'text-anchor', 'middle'
+ .attr 'x', 8
+ .attr 'y', (day) ->
+ day.y
+ .text (day) ->
+ day.text
+ .attr 'class', 'user-contrib-text'
+
+ renderMonths: ->
+ @svg.append 'g'
+ .selectAll 'text'
+ .data @months
+ .enter()
+ .append 'text'
+ .attr 'x', (date) ->
+ date.x
+ .attr 'y', 10
+ .attr 'class', 'user-contrib-text'
+ .text (date) =>
+ @monthNames[date.month]
+
+ renderKey: ->
+ keyColors = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
+ @svg.append 'g'
+ .attr 'transform', "translate(18, #{@daySizeWithSpace * 8 + 16})"
+ .selectAll 'rect'
+ .data keyColors
+ .enter()
+ .append 'rect'
+ .attr 'width', @daySize
+ .attr 'height', @daySize
+ .attr 'x', (color, i) =>
+ @daySizeWithSpace * i
+ .attr 'y', 0
+ .attr 'fill', (color) ->
+ color
+
+ initColor: ->
+ d3.scale
+ .linear()
+ .range(['#acd5f2', '#254e77'])
+ .domain([0, @highestValue])
+
+ initColorKey: ->
+ d3.scale
+ .linear()
+ .range(['#acd5f2', '#254e77'])
+ .domain([0, 3])
+
+ clickDay: (stamp) =>
+ if @currentSelectedDate isnt stamp.date
+ @currentSelectedDate = stamp.date
+ formatted_date = @currentSelectedDate.getFullYear() + "-" + (@currentSelectedDate.getMonth()+1) + "-" + @currentSelectedDate.getDate()
+
+ $.ajax
+ url: @calendar_activities_path
+ data:
+ date: formatted_date
+ cache: false
+ dataType: 'html'
+ beforeSend: ->
+ $('.user-calendar-activities').html '<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'
+ success: (data) ->
+ $('.user-calendar-activities').html data
+ else
+ $('.user-calendar-activities').html ''
+
+ initTooltips: ->
+ $('.js-contrib-calendar .js-tooltip').tooltip
+ html: true
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index b80b1b861cc..519618aa617 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -93,7 +93,9 @@ class @UsersSelect
$dropdown.glDropdown(
data: (term, callback) =>
- @users term, (users) =>
+ isAuthorFilter = $('.js-author-search')
+
+ @users term, term is '' and isAuthorFilter, (users) =>
if term.length is 0
showDivider = 0
@@ -138,7 +140,7 @@ class @UsersSelect
toggleLabel: (selected) ->
if selected && 'id' of selected
- selected.name
+ if selected.text then selected.text else selected.name
else
defaultLabel
@@ -219,7 +221,7 @@ class @UsersSelect
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) =>
- @users query.term, (users) =>
+ @users query.term, @projectId?, (users) =>
data = { results: users }
if query.term.length == 0
@@ -302,7 +304,7 @@ class @UsersSelect
# Return users list. Filtered by query
# Only active users retrieved
- users: (query, callback) =>
+ users: (query, fromProject, callback) =>
url = @buildUrl(@usersPath)
$.ajax(
@@ -311,7 +313,7 @@ class @UsersSelect
search: query
per_page: 20
active: true
- project_id: @projectId
+ project_id: @projectId if fromProject
group_id: @groupId
current_user: @showCurrentUser
author_id: @authorId
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 69b3b6586de..8b93665d085 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -8,9 +8,7 @@
*= require select2
*= require_self
*= require dropzone/basic
- *= require cal-heatmap
*= require cropper.css
- *= require animate
*/
/*
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 560de9fc0bd..3cbddc59f11 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -5,6 +5,7 @@
@import 'framework/tw_bootstrap';
@import "framework/layout";
+@import "framework/animations.scss";
@import "framework/avatar.scss";
@import "framework/blocks.scss";
@import "framework/buttons.scss";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
new file mode 100644
index 00000000000..1fec61bdba1
--- /dev/null
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -0,0 +1,72 @@
+// This file is based off animate.css 3.5.1, available here:
+// https://github.com/daneden/animate.css/blob/3.5.1/animate.css
+//
+// animate.css - http://daneden.me/animate
+// Version - 3.5.1
+// Licensed under the MIT license - http://opensource.org/licenses/MIT
+//
+// Copyright (c) 2016 Daniel Eden
+
+.animated {
+ -webkit-animation-duration: 1s;
+ animation-duration: 1s;
+ -webkit-animation-fill-mode: both;
+ animation-fill-mode: both;
+}
+
+.animated.infinite {
+ -webkit-animation-iteration-count: infinite;
+ animation-iteration-count: infinite;
+}
+
+.animated.hinge {
+ -webkit-animation-duration: 2s;
+ animation-duration: 2s;
+}
+
+.animated.flipOutX,
+.animated.flipOutY,
+.animated.bounceIn,
+.animated.bounceOut {
+ -webkit-animation-duration: .75s;
+ animation-duration: .75s;
+}
+
+@-webkit-keyframes pulse {
+ from {
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+
+ 50% {
+ -webkit-transform: scale3d(1.05, 1.05, 1.05);
+ transform: scale3d(1.05, 1.05, 1.05);
+ }
+
+ to {
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+}
+
+@keyframes pulse {
+ from {
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+
+ 50% {
+ -webkit-transform: scale3d(1.05, 1.05, 1.05);
+ transform: scale3d(1.05, 1.05, 1.05);
+ }
+
+ to {
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+}
+
+.pulse {
+ -webkit-animation-name: pulse;
+ animation-name: pulse;
+}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index f5ce70b606b..bb8d71fbae8 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -45,6 +45,7 @@
&.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; }
+ &.s70 { font-size: 34px; line-height: 70px; }
&.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
&.s140 { font-size: 72px; line-height: 140px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 434a26d57c6..6981f834d30 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -24,8 +24,8 @@
background-color: $background-color;
padding: $gl-padding;
margin-bottom: 0;
- border-top: 1px solid $border-color;
- border-bottom: 1px solid $border-color;
+ border-top: 1px solid $white-dark;
+ border-bottom: 1px solid $white-dark;
color: $gl-gray;
&.oneline-block {
@@ -110,9 +110,9 @@
.cover-title {
color: $gl-header-color;
margin: 0;
- font-size: 23px;
+ font-size: 24px;
font-weight: normal;
- margin: 16px 0 5px;
+ margin-bottom: 5px;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
@@ -137,7 +137,6 @@
}
.cover-desc {
- padding: 0 $gl-padding 3px;
color: $gl-text-color;
&.username:last-child {
@@ -205,7 +204,7 @@
.content-block {
padding: $gl-padding 0;
- border-bottom: 1px solid $border-color;
+ border-bottom: 1px solid $white-dark;
&.oneline-block {
line-height: 36px;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index eaf85bb17ca..467f3b35d74 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -16,6 +16,19 @@
@include btn-default;
}
+@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) {
+ background-color: $background;
+ color: $text;
+ border-color: $border;
+
+ &:hover,
+ &:focus {
+ background-color: $hover-background;
+ color: $hover-text;
+ border-color: $hover-border;;
+ }
+}
+
@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light;
border-color: $border-light;
@@ -106,11 +119,14 @@
@include btn-blue;
}
- &.btn-close,
&.btn-warning {
@include btn-orange;
}
+ &.btn-close {
+ @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
+ }
+
&.btn-danger,
&.btn-remove,
&.btn-red {
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 11f39d583bd..8642b7530e2 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,70 +1,44 @@
.calender-block {
+ padding-left: 0;
+ padding-right: 0;
+
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities {
- .calendar_onclick_hr {
- padding: 0;
- margin: 10px 0;
- }
-
.str-truncated {
max-width: 70%;
}
- .text-expander {
- background: #eee;
- color: #555;
- padding: 0 5px;
- cursor: pointer;
- margin-left: 4px;
- &:hover {
- background-color: #ddd;
- }
+ .user-calendar-activities-loading {
+ font-size: 24px;
}
}
-/**
-* This overwrites the default values of the cal-heatmap gem
-*/
-.calendar {
- .qi {
- fill: #fff;
- }
-
- .q1 {
- fill: #ededed !important;
- }
+.user-calendar {
+ text-align: center;
- .q2 {
- fill: #acd5f2 !important;
- }
-
- .q3 {
- fill: #7fa8d1 !important;
- }
-
- .q4 {
- fill: #49729b !important;
- }
-
- .q5 {
- fill: #254e77 !important;
+ .calendar {
+ display: inline-block;
}
+}
- .future {
- visibility: hidden;
+.user-contrib-cell {
+ &:hover {
+ cursor: pointer;
+ stroke: #000;
}
+}
- .domain-background {
- fill: none;
- shape-rendering: crispedges;
- }
+.user-contrib-text {
+ font-size: 12px;
+ fill: #959494;
+}
- .ch-tooltip {
- padding: 3px;
- font-weight: 550;
- }
+.calendar-hint {
+ margin-top: -23px;
+ float: right;
+ font-size: 12px;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 3386523dbf7..f8aecd0558d 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -289,7 +289,7 @@ table {
text-shadow: none;
@media (min-width: $screen-sm-min) {
- margin-top: 11px;
+ margin-top: 8px;
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 4bf3a050403..93c63c69843 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -154,7 +154,7 @@
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
- padding: 0 10px 10px;
+ padding: 0 10px;
}
.separator + .dropdown-header {
@@ -162,6 +162,10 @@
}
}
+.dropdown-menu-full-width {
+ width: 100%;
+}
+
.dropdown-menu-paging {
.dropdown-page-two,
.dropdown-menu-back {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 61d9954c6c8..71a9f79be3e 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -5,6 +5,10 @@
.file-holder {
border: 1px solid $border-color;
+ &.file-holder-no-border {
+ border: 0;
+ }
+
&.readme-holder {
margin: $gl-padding-top 0;
}
@@ -23,8 +27,17 @@
word-wrap: break-word;
border-radius: 3px 3px 0 0;
+ &.file-title-clear {
+ padding-left: 0;
+ padding-right: 0;
+ background-color: transparent;
+
+ .file-actions {
+ right: 0;
+ }
+ }
+
.file-actions {
- float: right;
position: absolute;
top: 5px;
right: 15px;
@@ -36,22 +49,6 @@
}
}
- .filename {
- &.old {
- display: inline-block;
- span.idiff {
- background-color: #f8cbcb;
- }
- }
-
- &.new {
- display: inline-block;
- span.idiff {
- background-color: #a6f3a6;
- }
- }
- }
-
a:not(.btn) {
color: $gl-dark-link-color;
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 558b133f593..46acc3b772f 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -28,10 +28,6 @@ input[type='text'].danger {
}
label {
- &.control-label {
- @extend .col-sm-2;
- }
-
&.inline-label {
margin: 0;
}
@@ -41,6 +37,10 @@ label {
}
}
+.control-label {
+ @extend .col-sm-2;
+}
+
.inline-input-group {
width: 250px;
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index c83cf881596..16cf394c426 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -9,8 +9,7 @@
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar {
.header-logo {
- background-color: $color;
- border-color: $color;
+ background: $color-darker;
a {
color: $color-light;
@@ -21,9 +20,13 @@
}
&:hover {
- background-color: $color-darker;
+ background-color: $color-dark;
a {
color: #fff;
+
+ h3 {
+ color: #fff;
+ }
}
}
}
@@ -87,8 +90,8 @@
}
$theme-blue: #2980b9;
-$theme-charcoal: #333c47;
-$theme-graphite: #888;
+$theme-charcoal: #3d454d;
+$theme-graphite: #666;
$theme-gray: #373737;
$theme-green: #019875;
$theme-violet: #548;
@@ -99,11 +102,11 @@ body {
}
&.ui_charcoal {
- @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272d);
+ @include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
}
&.ui_graphite {
- @include gitlab-theme(#ccc, $theme-graphite, #777, #666);
+ @include gitlab-theme(#ccc, #777, $theme-graphite, #555);
}
&.ui_gray {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 97f9d582007..0da96c4017d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -6,12 +6,12 @@ header {
transition-duration: .3s;
&.navbar-empty {
- height: 58px;
+ height: $header-height;
background: #fff;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid $btn-gray-hover;
.center-logo {
- margin: 11px 0;
+ margin: 8px 0;
text-align: center;
#tanuki-logo, img {
@@ -22,14 +22,18 @@ header {
}
&.navbar-gitlab {
- padding: 0 20px;
+ padding: 0 16px;
z-index: 100;
margin-bottom: 0;
- min-height: $header-height;
+ height: $header-height;
background-color: $background-color;
border: none;
border-bottom: 1px solid $border-color;
+ @media (max-width: $screen-xs-min) {
+ padding: 0 16px;
+ }
+
&.with-horizontal-nav {
border-bottom: none;
}
@@ -60,16 +64,44 @@ header {
margin: 6px 0;
border-radius: 0;
position: absolute;
- right: 2px;
+ right: -10px;
+ padding: 6px 10px;
&:hover {
- background-color: #eee;
+ background-color: $btn-gray-hover;
}
+
&.active {
color: $gl-icon-color;
}
}
}
+
+ &.header-collapsed {
+ padding: 0 16px;
+ }
+
+ .side-nav-toggle {
+ display: none;
+ position: absolute;
+ left: -10px;
+ margin: 6px 0;
+ padding: 6px 10px;
+ border: none;
+ background-color: $background-color;
+
+ &:hover {
+ background-color: $btn-gray-hover;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ }
+ }
}
.header-content {
@@ -77,6 +109,10 @@ header {
height: $header-height;
padding-right: 40px;
+ @media (max-width: $screen-xs-min) {
+ padding-left: 40px;
+ }
+
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
@@ -145,6 +181,10 @@ header {
@media (min-width: $screen-md-min) {
@include collapsed-header;
}
+
+ @media (max-width: $screen-xs-min) {
+ margin-left: 0;
+ }
}
.header-expanded {
@@ -153,6 +193,10 @@ header {
@media (min-width: $screen-md-min) {
margin-left: $sidebar_width;
}
+
+ @media (max-width: $screen-xs-min) {
+ margin-left: 0;
+ }
}
@media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 33cbee85987..bd531f8376b 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -48,10 +48,6 @@
display: block;
}
- .project-home-desc {
- font-size: 21px;
- }
-
.project-repo-buttons,
.git-clone-holder {
display: none;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 7c18e93a261..7eb7a8e4544 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -1,3 +1,34 @@
+@mixin fade($gradient-direction, $rgba, $gradient-color) {
+ visibility: visible;
+ opacity: 1;
+ position: absolute;
+ bottom: 12px;
+ width: 43px;
+ height: 30px;
+ transition-duration: .3s;
+ -webkit-transform: translateZ(0);
+ background: -webkit-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+ background: -o-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+ background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+ background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
+
+ &.end-scroll {
+ visibility: hidden;
+ opacity: 0;
+ transition-duration: .3s;
+ }
+}
+
+@mixin scrolling-links() {
+ white-space: nowrap;
+ overflow-x: auto;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+}
+
.nav-links {
padding: 0;
margin: 0;
@@ -119,7 +150,7 @@
}
input {
- height: 34px;
+ height: 35px;
display: inline-block;
position: relative;
top: 2px;
@@ -196,14 +227,22 @@
position: fixed;
top: $header-height;
width: 100%;
- z-index: 1;
+ z-index: 11;
background: $background-color;
border-bottom: 1px solid $border-color;
transition-duration: .3s;
+ .container-fluid {
+ position: relative;
+ }
+
.controls {
float: right;
- padding: 7px 5px 0 0;
+ padding: 7px 0 0;
+
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
i {
color: $layout-link-gray;
@@ -221,14 +260,31 @@
.dropdown {
margin-left: 7px;
+
+ @media (max-width: $screen-xs-min) {
+ margin-left: 0;
+ }
+
+ li.active {
+ font-weight: bold;
+ }
}
}
.nav-links {
+ @include scrolling-links();
border-bottom: none;
height: 51px;
- white-space: nowrap;
- overflow-x: auto;
+
+ .fade-right {
+ @include fade(left, rgba(250, 250, 250, 0.4), $background-color);
+ right: 0;
+ }
+
+ .fade-left {
+ @include fade(right, rgba(250, 250, 250, 0.4), $background-color);
+ left: 0;
+ }
li {
@@ -252,8 +308,62 @@
}
}
+ .nav-control {
+ .fade-right {
+
+ @media (min-width: $screen-xs-max) {
+ right: 67px;
+ }
+ @media (max-width: $screen-xs-min) {
+ right: 0;
+ }
+ }
+ }
+}
+
+.nav-block {
+ position: relative;
+
+ .nav-links {
+ @include scrolling-links();
+
+ .fade-right {
+ @include fade(left, rgba(255, 255, 255, 0.4), $white-light);
+ right: 0;
+ }
+
+ .fade-left {
+ @include fade(right, rgba(255, 255, 255, 0.4), $white-light);
+ left: 0;
+ }
+
+ &.event-filter {
+ .fade-right {
+ visibility: hidden;
+
+ @media (max-width: $screen-xs-max) {
+ visibility: visible;
+ }
+ }
+ }
+ }
}
.page-with-layout-nav {
- margin-top: 50px;
+ margin-top: $header-height + 2;
+
+ .right-sidebar {
+ top: ($header-height * 2) + 2;
+ }
+}
+
+.activities {
+
+ .nav-block {
+ border-bottom: 1px solid $border-color;
+
+ .nav-links {
+ border-bottom: none;
+ }
+ }
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index e940fd7286e..67f491b6d9c 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -98,7 +98,7 @@
}
.sidebar-user {
- padding: 9px 22px;
+ padding: 7px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
@@ -210,15 +210,33 @@
}
}
+.sidebar-wrapper {
+ &.hidden-nav {
+ width: 0;
+ }
+}
+
.page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ padding-left: 0;
+ }
+
.sidebar-wrapper {
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
+
.header-logo {
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
+
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
@@ -244,12 +262,22 @@
.collapse-nav a {
width: $sidebar_collapsed_width;
+
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
}
.sidebar-user {
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
.username {
display: none;
}
@@ -258,6 +286,10 @@
.layout-nav {
padding-right: $sidebar_collapsed_width;
+
+ @media (max-width: $screen-xs-min) {
+ padding-right: 0;;
+ }
}
}
@@ -268,6 +300,10 @@
padding-left: $sidebar_width;
}
+ @media (max-width: $screen-xs-min) {
+ padding-left: 0;
+ }
+
.sidebar-wrapper {
width: $sidebar_width;
@@ -276,7 +312,7 @@
}
.nav-sidebar li a {
- width: 230px;
+ width: $sidebar_width;
&.back-link {
i {
@@ -287,7 +323,17 @@
}
.layout-nav {
- padding-right: $sidebar_width;
+ @media (max-width: $screen-xs-min) {
+ padding-right: 0;
+ }
+
+ @media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
+ padding-right: 62px;
+ }
+
+ @media (min-width: $screen-md-min) {
+ padding-right: $sidebar_width;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 2779cd56788..3575984b229 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -269,3 +269,11 @@ h1, h2, h3, h4 {
text-align: right;
}
}
+
+.idiff.deletion {
+ background: $line-removed-dark;
+}
+
+.idiff.addition {
+ background: $line-added-dark;
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ccb4e5381b7..f253da814bc 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -2,7 +2,7 @@
* Layout
*/
$sidebar_collapsed_width: 62px;
-$sidebar_width: 230px;
+$sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
@@ -12,7 +12,7 @@ $gutter_inner_width: 258px;
*/
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
-$table-border-color: #ececec;
+$table-border-color: #f0f0f0;
$background-color: #fafafa;
/*
@@ -63,7 +63,8 @@ $gl-padding-top: 10px;
/*
* Misc
*/
-$row-hover: #f4f8fe;
+$row-hover: #f7faff;
+$row-hover-border: #b2d7ff;
$progress-color: #c0392b;
$avatar_radius: 50%;
$header-height: 50px;
@@ -104,7 +105,7 @@ $blue-medium-light: #3498cb;
$blue-medium: #2f8ebf;
$blue-medium-dark: #2d86b4;
-$orange-light: rgba(252, 109, 38, 0.80);
+$orange-light: #fc8a51;
$orange-normal: #e75e40;
$orange-dark: #ce5237;
@@ -119,8 +120,8 @@ $border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
$border-white-dark: #c6cacf;
-$border-gray-light: rgba(0, 0, 0, 0.06);
-$border-gray-normal: rgba(0, 0, 0, 0.10);;
+$border-gray-light: #dcdcdc;
+$border-gray-normal: rgba(0, 0, 0, 0.10);
$border-gray-dark: #c6cacf;
$border-green-light: #2faa60;
@@ -178,6 +179,7 @@ $table-border-gray: #f0f0f0;
$line-target-blue: #eaf3fc;
$line-select-yellow: #fcf8e7;
$line-select-yellow-dark: #f0e2bd;
+
/*
* Fonts
*/
@@ -215,6 +217,7 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
$btn-active-gray: #ececec;
$btn-placeholder-gray: #c7c7c7;
$btn-white-active: #848484;
+$btn-gray-hover: #eee;
/*
* Award emoji
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index f870ea0d87f..ff02ebdd34c 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -32,7 +32,7 @@
}
}
-.zen-cotrol {
+.zen-control {
padding: 0;
color: #555;
background: none;
diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss
new file mode 100644
index 00000000000..28611a5ec81
--- /dev/null
+++ b/app/assets/stylesheets/mailers/devise.scss
@@ -0,0 +1,134 @@
+// NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout
+// used for Devise email templates, and _should not_ be included in any
+// application stylesheets.
+//
+// Styles defined here are embedded directly into the resulting email HTML via
+// the `premailer` gem.
+
+$body-background-color: #363636;
+$message-background-color: #fafafa;
+
+$header-color: #6b4fbb;
+$body-color: #444;
+$cta-color: #e14329;
+$footer-link-color: #7e7e7e;
+
+$font-family: Helvetica, Arial, sans-serif;
+
+body {
+ background-color: $body-background-color;
+ font-family: $font-family;
+ margin: 0;
+ padding: 0;
+}
+
+table {
+ -premailer-cellpadding: 0;
+ -premailer-cellspacing: 0;
+
+ border: 0;
+ border-collapse: separate;
+
+ &#wrapper {
+ background-color: $body-background-color;
+ width: 100%;
+ }
+
+ &#header {
+ margin: 0 auto;
+ text-align: left;
+ width: 600px;
+ }
+
+ &#body {
+ background-color: $message-background-color;
+ border: 1px solid #000;
+ border-radius: 4px;
+ margin: 0 auto;
+ width: 600px;
+ }
+
+ &#footer {
+ color: $footer-link-color;
+ font-size: 14px;
+ text-align: center;
+ width: 100%;
+ }
+
+ td {
+ &#body-container {
+ padding: 20px 40px;
+ }
+ }
+}
+
+.center {
+ text-align: center;
+}
+
+#logo {
+ border: none;
+ outline: none;
+ min-height: 88px;
+ width: 134px;
+}
+
+#content {
+ h2 {
+ color: $header-color;
+ font-size: 30px;
+ font-weight: 400;
+ line-height: 34px;
+ margin-top: 0;
+ }
+
+ p {
+ color: $body-color;
+ font-size: 17px;
+ line-height: 24px;
+ margin-bottom: 0;
+ }
+}
+
+#cta {
+ border: 1px solid $cta-color;
+ border-radius: 3px;
+ display: inline-block;
+ margin: 20px 0;
+ padding: 12px 24px;
+
+ a {
+ background-color: $message-background-color;
+ color: $cta-color;
+ display: inline-block;
+ text-decoration: none;
+ }
+}
+
+#tanuki {
+ padding: 40px 0 0;
+
+ img {
+ border: none;
+ outline: none;
+ width: 37px;
+ min-height: 36px;
+ }
+}
+
+#tagline {
+ font-size: 22px;
+ font-weight: 100;
+ padding: 4px 0 40px;
+}
+
+#social {
+ padding: 0 10px 20px;
+ width: 600px;
+ word-spacing: 20px;
+
+ a {
+ color: $footer-link-color;
+ text-decoration: none;
+ }
+}
diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss
new file mode 100644
index 00000000000..001994db97b
--- /dev/null
+++ b/app/assets/stylesheets/mailers/repository_push_email.scss
@@ -0,0 +1,43 @@
+@import "framework/variables";
+
+table.code {
+ width: 100%;
+ font-family: monospace;
+ border: none;
+ border-collapse: separate;
+ margin: 0;
+ padding: 0;
+ -premailer-cellpadding: 0;
+ -premailer-cellspacing: 0;
+ -premailer-width: 100%;
+
+ td {
+ line-height: $code_line_height;
+ font-family: monospace;
+ font-size: $code_font_size;
+ }
+
+ td.diff-line-num {
+ margin: 0;
+ padding: 0;
+ border: none;
+ background: $background-color;
+ color: rgba(0, 0, 0, 0.3);
+ padding: 0 5px;
+ border-right: 1px solid $border-color;
+ text-align: right;
+ min-width: 35px;
+ max-width: 50px;
+ width: 35px;
+ }
+
+ td.line_content {
+ display: block;
+ margin: 0;
+ padding: 0 0.5em;
+ border: none;
+ white-space: pre;
+ }
+}
+
+@import "highlight/white";
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index c2cd227571f..fc3f214aba5 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -26,8 +26,28 @@
.commit-info-row {
margin-bottom: 10px;
+
+ &.commit-info-row-header {
+ line-height: 34px;
+
+ @media (min-width: $screen-sm-min) {
+ margin-bottom: 0;
+ }
+
+ .commit-options-dropdown-caret {
+ @media (max-width: $screen-sm) {
+ margin-left: 0;
+ }
+ }
+ }
+
.avatar {
@extend .avatar-inline;
+ margin-left: 0;
+
+ @media (min-width: $screen-sm-min) {
+ margin-left: 4px;
+ }
}
.commit-committer-link,
.commit-author-link {
@@ -35,10 +55,6 @@
font-weight: bold;
}
- .time_ago {
- margin-left: 8px;
- }
-
.fa-clipboard {
color: $dropdown-title-btn-color;
}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5e61e61d85c..1b389d83525 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -29,8 +29,6 @@
margin-top: 6px;
p {
- overflow-x: auto;
-
&:last-child {
margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 8981f070a20..22679c764dc 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -23,7 +23,7 @@
.file-title {
@extend .monospace;
- line-height: 42px;
+ line-height: 35px;
padding-top: 7px;
padding-bottom: 7px;
@@ -43,7 +43,7 @@
.editor-file-name {
@extend .monospace;
-
+
float: left;
margin-right: 10px;
}
@@ -59,7 +59,22 @@
}
.encoding-selector,
- .license-selector {
+ .license-selector,
+ .gitignore-selector {
display: inline-block;
+ vertical-align: top;
+ font-family: $regular_font;
+ }
+
+ .gitignore-selector {
+
+ .dropdown {
+ line-height: 21px;
+ }
+
+ .dropdown-menu-toggle {
+ vertical-align: top;
+ width: 220px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 1cf3023ecc9..787c387379e 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -125,7 +125,7 @@
.right-sidebar {
position: fixed;
- top: 58px;
+ top: $header-height;
bottom: 0;
right: 0;
z-index: 10;
@@ -150,6 +150,10 @@
font-weight: 600;
}
+ .light {
+ font-weight: normal;
+ }
+
.sidebar-collapsed-icon {
display: none;
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index fc9db97132d..4e35ca329e4 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -40,11 +40,6 @@
}
}
-.issue-search-form {
- margin: 0;
- height: 24px;
-}
-
form.edit-issue {
margin: 0;
}
@@ -96,8 +91,3 @@ form.edit-issue {
.issue-form .select2-container {
width: 250px !important;
}
-
-.issue-closed-by-widget {
- color: $gl-text-color;
- margin-left: 52px;
-}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index c4005ba1e69..8046e203a99 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -41,7 +41,7 @@
margin: 0;
margin-left: 20px;
padding: 5px;
- padding-top: 12px;
+ padding-top: 8px;
line-height: 20px;
&.right {
@@ -110,6 +110,29 @@
p:last-child {
margin-bottom: 0;
}
+
+ @media (max-width: $screen-sm-max) {
+ h4 {
+ font-size: 15px;
+ }
+
+ p {
+ font-size: 13px;
+ }
+
+ .btn,
+ .btn-group,
+ .accept-action {
+ width: 100%;
+ margin-bottom: 4px;
+ }
+
+ .accept-control {
+ width: 100%;
+ text-align: center;
+ margin: 0;
+ }
+ }
}
.mr-widget-footer {
@@ -280,11 +303,5 @@
background-color: $white-light;
color: $gl-placeholder-color;
}
-
- th,
- td {
- padding: 16px;
- }
}
}
-
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 624c8249f7e..a3e1ac13a43 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -226,8 +226,7 @@ ul.notes {
}
}
-.note-action-button,
-.discussion-action-button {
+.note-action-button {
display: inline-block;
margin-left: 10px;
line-height: 24px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
new file mode 100644
index 00000000000..6128868b670
--- /dev/null
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -0,0 +1,24 @@
+.pipelines {
+ .stage {
+ max-width: 100px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .duration, .finished_at {
+ margin: 4px 0;
+ }
+
+ .commit-title {
+ margin: 0;
+ }
+
+ .controls {
+ white-space: nowrap;
+ }
+
+ .btn {
+ margin: 4px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index abc5a0e9877..167ab40d881 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -66,12 +66,6 @@
}
}
-.calendar-hint {
- margin-top: -12px;
- float: right;
- font-size: 12px;
-}
-
.profile-link-holder {
display: inline;
@@ -134,14 +128,6 @@
}
}
-.change-username-title {
- color: $gl-warning;
-}
-
-.remove-account-title {
- color: $gl-danger;
-}
-
.provider-btn-group {
display: inline-block;
margin-right: 10px;
@@ -218,7 +204,7 @@
.btn {
display: inline-block;
- width: 48%;
+ width: 46%;
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index c20f04653fc..edef336481d 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -7,10 +7,10 @@
}
.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
- margin-bottom: 16px;
+ margin-bottom: 0;
}
.new_project,
-.edit_project {
+.edit-project {
fieldset.features {
.control-label {
font-weight: normal;
@@ -26,8 +26,13 @@
}
.project-home-panel {
- padding-bottom: 40px;
- border-bottom: 1px solid $border-color;
+ background: $white-light;
+ text-align: left;
+ padding: 24px 0;
+
+ .container-fluid {
+ position: relative;
+ }
.cover-controls {
.project-settings-dropdown {
@@ -43,21 +48,55 @@
}
}
- .project-identicon-holder {
- margin-bottom: 16px;
+ .cover-title {
+ margin-bottom: 0;
+ }
- .avatar, .identicon {
- margin: 0 auto;
- float: none;
+ .project-image-container {
+ @include make-sm-column(1);
+ max-width: 86px;
+ min-width: 86px;
+ padding-right: 0;
+ margin: 11px 0;
+
+ @media (max-width: $screen-md-max) {
+ padding-left: 0;
+ margin: 0 0 10px;
+ max-width: none;
+ min-width: none;
+
+ .avatar.s70 {
+ margin: auto;
+ }
}
+ }
- .identicon {
- @include border-radius(50%);
+ .project-info {
+ @include make-sm-column(10);
+
+ h1 {
+ font-size: 24px;
+ font-weight: normal;
+ margin: 0;
+ }
+
+ .project-home-desc {
+ p {
+ margin: 0;
+ }
}
}
+ .identicon {
+ float: left;
+ @include border-radius(50%);
+ }
+
+ .avatar {
+ float: none;
+ }
+
.notifications-btn {
- margin-top: -28px;
.fa-bell {
margin-right: 6px;
@@ -69,28 +108,45 @@
}
.project-repo-buttons {
- margin-top: 20px;
- margin-bottom: 0;
+ font-size: 0;
- .count-buttons {
- display: block;
- margin-bottom: 20px;
- }
+ .btn {
+ @include btn-gray;
+ padding: 3px 10px;
+ text-transform: none;
+ background-color: $background-color;
- .clone-row {
- .split-repo-buttons,
- .project-clone-holder {
- display: inline-block;
+ .fa {
+ color: $layout-link-gray;
}
- .split-repo-buttons {
- margin: 0 12px;
+ .fa-caret-down {
+ margin-left: 3px;
}
}
- .btn {
- @include btn-gray;
- text-transform: none;
+ .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+
+ form {
+ margin-left: 10px;
+ }
+
+ .count-buttons {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 16px;
+ }
+
+ .project-clone-holder {
+ display: inline-block;
+ margin-top: 16px;
+
+ input {
+ height: 29px;
+ }
}
.count-with-arrow {
@@ -140,14 +196,18 @@
line-height: 13px;
padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px;
- padding: 10px 14px;
+ padding: 7px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
white-space: nowrap;
- margin: 0 11px 0 4px;
+ margin: 0 10px 0 4px;
+
+ a {
+ color: inherit;
+ }
&:hover {
background: #fff;
@@ -155,13 +215,37 @@
}
}
}
+
+ .project-right-buttons {
+ position: absolute;
+ right: 16px;
+ bottom: 0;
+
+ .btn {
+ padding: 3px 10px;
+ background-color: $background-color;
+ }
+
+ @media (max-width: 1304px) {
+ top: 0;
+ }
+ }
+
+ @media (max-width: $screen-md-max) {
+ text-align: center;
+
+ .project-info,
+ .project-image-container {
+ width: 100%;
+ }
+ }
}
.split-one {
display: inline-table;
margin-right: 12px;
- a {
+ > a {
margin: -1px;
}
}
@@ -285,11 +369,11 @@ a.deploy-project-label {
}
.project-stats {
- text-align: center;
margin-top: $gl-padding;
margin-bottom: 0;
- padding-top: 10px;
- padding-bottom: 4px;
+ padding: 16px 0;
+ background-color: $white-light;
+ font-size: 0;
ul.nav {
display: inline-block;
@@ -300,12 +384,11 @@ a.deploy-project-label {
}
.nav > li > a {
- @include btn-default;
- @include btn-gray;
-
background-color: transparent;
- border: 1px solid #f7f8fa;
- margin-left: 12px;
+ margin-right: 12px;
+ padding: 0 10px;
+ font-size: 15px;
+ color: $notes-light-color;
}
li {
@@ -325,6 +408,10 @@ a.deploy-project-label {
background-color: #f0f2f5;
}
}
+
+ &.row-content-block.second-block {
+ margin-top: 0;
+ }
}
pre.light-well {
@@ -442,9 +529,14 @@ pre.light-well {
border-top: 0;
.edit-project-readme {
- z-index: 100;
+ z-index: 2;
position: relative;
}
+
+ .wiki h1 {
+ border-bottom: none;
+ padding: 0;
+ }
}
.git-clone-holder {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 2bff70c8c64..037ad520545 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -28,6 +28,7 @@
}
.search-input {
+ padding-right: 20px;
border: none;
font-size: 14px;
outline: none;
@@ -47,6 +48,7 @@
display: inline-block;
background-color: $location-badge-bg;
vertical-align: top;
+ cursor: default;
}
.search-input-container {
@@ -55,7 +57,7 @@
position: relative;
}
- .search-location-badge, .search-input-wrap {
+ .search-input-wrap {
// Fallback if flexbox is not supported
display: inline-block;
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 3fb70085713..2e8f356298d 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -12,3 +12,11 @@
border: 1px solid $warning-message-border;
border-radius: $border-radius-base;
}
+
+.warning-title {
+ color: $gl-warning;
+}
+
+.danger-title {
+ color: $gl-danger;
+}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 639d639d5b0..2aa939b7dc3 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -16,19 +16,6 @@
}
}
-.snippet-box {
- @include border-radius(2px);
-
- display: block;
- float: left;
- padding: 0 $gl-padding;
- font-weight: normal;
- margin-right: 10px;
- font-size: $gl-font-size;
- border: 1px solid;
- line-height: 32px;
-}
-
.markdown-snippet-copy {
position: fixed;
top: -10px;
@@ -36,3 +23,34 @@
max-height: 0;
max-width: 0;
}
+
+.file-holder.snippet-file-content {
+ padding-bottom: $gl-padding;
+ border-bottom: 1px solid $border-color;
+
+ .file-title {
+ padding-top: $gl-padding;
+ padding-bottom: $gl-padding;
+ }
+
+ .file-actions {
+ top: 12px;
+ }
+
+ .file-content {
+ border-left: 1px solid $border-color;
+ border-right: 1px solid $border-color;
+ border-bottom: 1px solid $border-color;
+ }
+}
+
+.snippet-title {
+ font-size: 24px;
+ font-weight: normal;
+}
+
+.snippet-actions {
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ }
+}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index e51c3491dae..afc00a68572 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -29,6 +29,17 @@
.todo-item {
.todo-title {
@include str-truncated(calc(100% - 174px));
+ overflow: visible;
+ }
+
+ .status-box {
+ margin: 0;
+ float: none;
+ display: inline-block;
+ font-weight: normal;
+ padding: 0 5px;
+ line-height: inherit;
+ font-size: 14px;
}
.todo-body {
@@ -76,12 +87,11 @@
@media (max-width: $screen-xs-max) {
.todo-item {
- padding-left: $gl-padding;
-
.todo-title {
white-space: normal;
overflow: visible;
max-width: 100%;
+ margin-bottom: 10px;
}
.avatar {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index a84fc2e0318..f16fc7f388f 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -15,16 +15,23 @@
margin-bottom: 0;
tr {
- > td, > th {
+ border-bottom: 1px solid $table-border-gray;
+ border-top: 1px solid $table-border-gray;
+
+ td, th {
line-height: 23px;
}
&:hover {
+ cursor: pointer;
+
td {
- background: $row-hover;
+ background-color: $row-hover;
+ border-top: 1px solid $row-hover-border;
+ border-bottom: 1px solid $row-hover-border;
}
- cursor: pointer;
}
+
&.selected {
td {
background: $gray-dark;
diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb
index e9b0972bdd8..5055c318a5f 100644
--- a/app/controllers/admin/abuse_reports_controller.rb
+++ b/app/controllers/admin/abuse_reports_controller.rb
@@ -9,6 +9,6 @@ class Admin::AbuseReportsController < Admin::ApplicationController
abuse_report.remove_user(deleted_by: current_user) if params[:remove_user]
abuse_report.destroy
- render nothing: true
+ head :ok
end
end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index ec22548ddeb..0a34a12e2a7 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path
end
+ def reset_health_check_token
+ @application_setting.reset_health_check_access_token!
+ flash[:notice] = 'New health check access token has been generated!'
+ redirect_to :back
+ end
+
def clear_repository_check_states
RepositoryCheck::ClearWorker.perform_async
@@ -53,6 +59,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
end
+ enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
+
+ params[:application_setting][:disabled_oauth_sign_in_sources] =
+ AuthHelper.button_based_providers.map(&:to_s) -
+ Array(enabled_oauth_sign_in_sources)
+
params.require(:application_setting).permit(
:default_projects_limit,
:default_branch_protection,
@@ -94,8 +106,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:email_author_in_body,
:repository_checks_enabled,
:metrics_packet_size,
+ :send_user_confirmation_email,
+ :container_registry_token_expire_delay,
restricted_visibility_levels: [],
- import_sources: []
+ import_sources: [],
+ disabled_oauth_sign_in_sources: []
)
end
end
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index fc342924987..82055006ac0 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -32,7 +32,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
respond_to do |format|
format.html { redirect_back_or_default(default: { action: 'index' }) }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
new file mode 100644
index 00000000000..241c7be0ea1
--- /dev/null
+++ b/app/controllers/admin/health_check_controller.rb
@@ -0,0 +1,5 @@
+class Admin::HealthCheckController < Admin::ApplicationController
+ def show
+ @errors = HealthCheck::Utils.process_checks('standard')
+ end
+end
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index cb33fdd9763..054bb52b696 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -6,7 +6,7 @@ class Admin::KeysController < Admin::ApplicationController
respond_to do |format|
format.html
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index a701d49b844..7345c91f67d 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -9,23 +9,18 @@ class Admin::RunnersController < Admin::ApplicationController
end
def show
- @builds = @runner.builds.order('id DESC').first(30)
- @projects =
- if params[:search].present?
- ::Project.search(params[:search])
- else
- Project.all
- end
- @projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
- @projects = @projects.page(params[:page]).per(30)
+ assign_builds_and_projects
end
def update
- @runner.update_attributes(runner_params)
-
- respond_to do |format|
- format.js
- format.html { redirect_to admin_runner_path(@runner) }
+ if @runner.update_attributes(runner_params)
+ respond_to do |format|
+ format.js
+ format.html { redirect_to admin_runner_path(@runner) }
+ end
+ else
+ assign_builds_and_projects
+ render 'show'
end
end
@@ -58,6 +53,18 @@ class Admin::RunnersController < Admin::ApplicationController
end
def runner_params
- params.require(:runner).permit(:token, :description, :tag_list, :active)
+ params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
+ end
+
+ def assign_builds_and_projects
+ @builds = runner.builds.order('id DESC').first(30)
+ @projects =
+ if params[:search].present?
+ ::Project.search(params[:search])
+ else
+ Project.all
+ end
+ @projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
+ @projects = @projects.page(params[:page]).per(30)
end
end
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 377e9741e5f..3a2f0185315 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -11,7 +11,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else
spam_log.destroy
- render nothing: true
+ head :ok
end
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index f2f654c7bcd..f35f4a8c811 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -119,6 +119,7 @@ class Admin::UsersController < Admin::ApplicationController
user_params_with_pass.merge!(
password: params[:user][:password],
password_confirmation: params[:user][:password_confirmation],
+ password_expires_at: Time.now
)
end
@@ -153,7 +154,7 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 17b3f49aed1..c28d1ca9e3b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -176,7 +176,7 @@ class ApplicationController < ActionController::Base
end
def check_password_expiration
- if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
+ if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
redirect_to new_profile_password_path and return
end
end
@@ -232,7 +232,7 @@ class ApplicationController < ActionController::Base
end
def configure_permitted_parameters
- devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me, :otp_attempt) }
+ devise_parameter_sanitizer.permit(:sign_in, keys: [:username, :email, :password, :login, :remember_me, :otp_attempt])
end
def hexdigest(string)
@@ -263,7 +263,7 @@ class ApplicationController < ActionController::Base
# internal repos where you are not a member. Enable this filter
# or improve current implementation to filter only issues you
# created or assigned or mentioned
- #@filter_params[:authorized_only] = true
+ # @filter_params[:authorized_only] = true
end
@filter_params
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index eb0abc80ab4..3865b2d61fd 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -31,6 +31,24 @@ class AutocompleteController < ApplicationController
render json: @user, only: [:name, :username, :id], methods: [:avatar_url]
end
+ def projects
+ project = Project.find_by_id(params[:project_id])
+
+ projects = current_user.authorized_projects
+ projects = projects.select do |project|
+ current_user.can?(:admin_issue, project)
+ end
+
+ no_project = {
+ id: 0,
+ name_with_namespace: 'No project',
+ }
+ projects.unshift(no_project)
+ projects.delete(project)
+
+ render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
+ end
+
private
def find_users
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 787416c17ab..dacb5679dd3 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -122,7 +122,7 @@ module CreatesCommit
# Merge request from fork to this project
@mr_source_project = @tree_edit_project
@mr_target_project = @project
- @mr_target_branch ||= @ref
+ @mr_target_branch ||= @ref
end
end
end
diff --git a/app/controllers/concerns/toggle_subscription_action.rb b/app/controllers/concerns/toggle_subscription_action.rb
index 8a43c0b93c4..9e3b9be2ff4 100644
--- a/app/controllers/concerns/toggle_subscription_action.rb
+++ b/app/controllers/concerns/toggle_subscription_action.rb
@@ -6,7 +6,7 @@ module ToggleSubscriptionAction
subscribable_resource.toggle_subscription(current_user)
- render nothing: true
+ head :ok
end
private
diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb
index 23a4ef21ea2..2a88350a4ca 100644
--- a/app/controllers/dashboard/labels_controller.rb
+++ b/app/controllers/dashboard/labels_controller.rb
@@ -1,6 +1,6 @@
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
- labels = Label.where(project_id: projects).select(:title, :color).uniq(:title)
+ labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title)
respond_to do |format|
format.json { render json: labels }
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 71acc244a91..c08eb811532 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -28,7 +28,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def starred
- @projects = current_user.starred_projects.sorted_by_activity
+ @projects = current_user.viewable_starred_projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 5abf97342c3..f9a1929c117 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -12,7 +12,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: todo_notice }
- format.js { render nothing: true }
+ format.js { head :ok }
format.json do
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
@@ -24,7 +24,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
- format.js { render nothing: true }
+ format.js { head :ok }
format.json do
find_todos
render json: { count: @todos.size, done_count: current_user.todos.done.count }
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 1dce4a21729..4dda4e51f6a 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -25,7 +25,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events
projects =
if params[:filter] == "starred"
- current_user.starred_projects
+ current_user.viewable_starred_projects
else
current_user.authorized_projects
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index d5ef33888c6..48dbf656e84 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -40,7 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
new file mode 100644
index 00000000000..037da7d2bce
--- /dev/null
+++ b/app/controllers/health_check_controller.rb
@@ -0,0 +1,22 @@
+class HealthCheckController < HealthCheck::HealthCheckController
+ before_action :validate_health_check_access!
+
+ private
+
+ def validate_health_check_access!
+ render_404 unless token_valid?
+ end
+
+ def token_valid?
+ token = params[:token].presence || request.headers['TOKEN']
+ token.present? &&
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(
+ token,
+ current_application_settings.health_check_access_token
+ )
+ end
+
+ def render_404
+ render file: Rails.root.join('public', '404'), layout: false, status: '404'
+ end
+end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
new file mode 100644
index 00000000000..cee3b6c43e7
--- /dev/null
+++ b/app/controllers/jwt_controller.rb
@@ -0,0 +1,87 @@
+class JwtController < ApplicationController
+ skip_before_action :authenticate_user!
+ skip_before_action :verify_authenticity_token
+ before_action :authenticate_project_or_user
+
+ SERVICES = {
+ Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
+ }
+
+ def auth
+ service = SERVICES[params[:service]]
+ return head :not_found unless service
+
+ result = service.new(@project, @user, auth_params).execute
+
+ render json: result, status: result[:http_status]
+ end
+
+ private
+
+ def authenticate_project_or_user
+ authenticate_with_http_basic do |login, password|
+ # if it's possible we first try to authenticate project with login and password
+ @project = authenticate_project(login, password)
+ return if @project
+
+ @user = authenticate_user(login, password)
+ return if @user
+
+ render_403
+ end
+ end
+
+ def auth_params
+ params.permit(:service, :scope, :account, :client_id)
+ end
+
+ def authenticate_project(login, password)
+ if login == 'gitlab-ci-token'
+ Project.find_by(builds_enabled: true, runners_token: password)
+ end
+ end
+
+ def authenticate_user(login, password)
+ # TODO: this is a copy and paste from grack_auth,
+ # it should be refactored in the future
+
+ user = Gitlab::Auth.new.find(login, password)
+
+ # If the user authenticated successfully, we reset the auth failure count
+ # from Rack::Attack for that IP. A client may attempt to authenticate
+ # with a username and blank password first, and only after it receives
+ # a 401 error does it present a password. Resetting the count prevents
+ # false positives from occurring.
+ #
+ # Otherwise, we let Rack::Attack know there was a failed authentication
+ # attempt from this IP. This information is stored in the Rails cache
+ # (Redis) and will be used by the Rack::Attack middleware to decide
+ # whether to block requests from this IP.
+ config = Gitlab.config.rack_attack.git_basic_auth
+
+ if config.enabled
+ if user
+ # A successful login will reset the auth failure count from this IP
+ Rack::Attack::Allow2Ban.reset(request.ip, config)
+ else
+ banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do
+ # Unless the IP is whitelisted, return true so that Allow2Ban
+ # increments the counter (stored in Rails.cache) for the IP
+ if config.ip_whitelist.include?(request.ip)
+ false
+ else
+ true
+ end
+ end
+
+ if banned
+ Rails.logger.info "IP #{request.ip} failed to login " \
+ "as #{login} but has been temporarily banned from Git auth"
+ return
+ end
+ end
+ end
+
+ user
+ end
+end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index df98f56a1cd..f35d631df0c 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -97,7 +97,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
handle_signup_error
end
- def handle_service_ticket provider, ticket
+ def handle_service_ticket(provider, ticket)
Gitlab::OAuth::Session.create provider, ticket
session[:service_tickets] ||= {}
session[:service_tickets][provider] = ticket
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index 0ede9b8e21b..1c24c4db993 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -24,7 +24,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
respond_to do |format|
format.html { redirect_to profile_emails_url }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index a12549d6bcb..830e0b9591b 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -32,7 +32,7 @@ class Profiles::KeysController < Profiles::ApplicationController
respond_to do |format|
format.html { redirect_to profile_keys_url }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index be872a93fee..776ba92c9ab 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -26,7 +26,7 @@ class Projects::ApplicationController < ApplicationController
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
- if @project && can?(current_user, :read_project, @project)
+ if can?(current_user, :read_project, @project) && !@project.pending_delete?
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace)
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index d09e7375b67..dd9508da049 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -50,7 +50,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace,
@project), status: 303
end
- format.js { render status: status[:return_code] }
+ format.js { render nothing: true, status: status[:return_code] }
end
end
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index b8b9e78427d..bb1f6c5e980 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -38,6 +38,14 @@ class Projects::BuildsController < Projects::ApplicationController
end
end
+ def trace
+ respond_to do |format|
+ format.json do
+ render json: @build.trace_with_state(params[:state]).merge!(id: @build.id, status: @build.status)
+ end
+ end
+ end
+
def retry
unless @build.retryable?
return render_404
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index a202cb38692..10b5932affa 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -17,12 +17,12 @@ class Projects::CommitController < Projects::ApplicationController
def show
apply_diff_view_cookie!
- @line_notes = commit.notes.inline
+ @grouped_diff_notes = commit.notes.grouped_diff_notes
+
@note = @project.build_commit_note(commit)
- @notes = commit.notes.not_inline.fresh
+ @notes = commit.notes.non_diff_notes.fresh
@noteable = @commit
- @comments_allowed = @reply_allowed = true
- @comments_target = {
+ @comments_target = {
noteable_type: 'Commit',
commit_id: @commit.id
}
@@ -67,10 +67,10 @@ class Projects::CommitController < Projects::ApplicationController
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
success_path: successful_change_path, failure_path: failed_change_path)
end
-
+
def cherry_pick
assign_change_commit_vars(@commit.cherry_pick_branch_name)
-
+
return render_404 if @target_branch.blank?
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 671d5c23024..af0b69a2442 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -22,7 +22,8 @@ class Projects::CompareController < Projects::ApplicationController
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit]
- @line_notes = []
+ @diff_notes_disabled = true
+ @grouped_diff_notes = {}
end
end
diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb
new file mode 100644
index 00000000000..d1f46497207
--- /dev/null
+++ b/app/controllers/projects/container_registry_controller.rb
@@ -0,0 +1,34 @@
+class Projects::ContainerRegistryController < Projects::ApplicationController
+ before_action :verify_registry_enabled
+ before_action :authorize_read_container_image!
+ before_action :authorize_update_container_image!, only: [:destroy]
+ layout 'project'
+
+ def index
+ @tags = container_registry_repository.tags
+ end
+
+ def destroy
+ url = namespace_project_container_registry_index_path(project.namespace, project)
+
+ if tag.delete
+ redirect_to url
+ else
+ redirect_to url, alert: 'Failed to remove tag'
+ end
+ end
+
+ private
+
+ def verify_registry_enabled
+ render_404 unless Gitlab.config.registry.enabled
+ end
+
+ def container_registry_repository
+ @container_registry_repository ||= project.container_registry_repository
+ end
+
+ def tag
+ @tag ||= container_registry_repository.tag(params[:id])
+ end
+end
diff --git a/app/controllers/projects/find_file_controller.rb b/app/controllers/projects/find_file_controller.rb
index 54a0c447aee..cf53ad0a670 100644
--- a/app/controllers/projects/find_file_controller.rb
+++ b/app/controllers/projects/find_file_controller.rb
@@ -1,26 +1,26 @@
-# Controller for viewing a repository's file structure
-class Projects::FindFileController < Projects::ApplicationController
- include ExtractsPath
- include ActionView::Helpers::SanitizeHelper
- include TreeHelper
-
- before_action :require_non_empty_project
- before_action :assign_ref_vars
- before_action :authorize_download_code!
-
- def show
- return render_404 unless @repository.commit(@ref)
-
- respond_to do |format|
- format.html
- end
- end
-
- def list
- file_paths = @repo.ls_files(@ref)
-
- respond_to do |format|
- format.json { render json: file_paths }
- end
- end
-end
+# Controller for viewing a repository's file structure
+class Projects::FindFileController < Projects::ApplicationController
+ include ExtractsPath
+ include ActionView::Helpers::SanitizeHelper
+ include TreeHelper
+
+ before_action :require_non_empty_project
+ before_action :assign_ref_vars
+ before_action :authorize_download_code!
+
+ def show
+ return render_404 unless @repository.commit(@ref)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ def list
+ file_paths = @repo.ls_files(@ref)
+
+ respond_to do |format|
+ format.json { render json: file_paths }
+ end
+ end
+end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index dfa9bd259e8..a60027ff477 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController
if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user)
- if status
- flash[:notice] = 'Hook successfully executed.'
+ if status && status >= 200 && status < 400
+ flash[:notice] = "Hook executed successfully: HTTP #{status}"
+ elsif status
+ flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
@@ -61,7 +63,8 @@ class Projects::HooksController < Projects::ApplicationController
:push_events,
:tag_push_events,
:token,
- :url
+ :url,
+ :wiki_page_events
)
end
end
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 7756f0f0ed3..a1b84afcd91 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -20,6 +20,7 @@ class Projects::ImportsController < Projects::ApplicationController
@project.import_retry
else
@project.import_start
+ @project.add_import_job
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9c147b3689e..d54284d7b20 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -73,12 +73,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# but we need it for the "View file @ ..." link by deleted files
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
- @comments_allowed = @reply_allowed = true
@comments_target = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
- @line_notes = @merge_request.notes.where("line_code is not null")
+
+ @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
respond_to do |format|
format.html
@@ -117,6 +117,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
+ @diff_notes_disabled = true
@ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit
@@ -204,7 +205,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def branch_from
- #This is always source
+ # This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
@@ -228,6 +229,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if ci_commit
status = ci_commit.status
coverage = ci_commit.try(:coverage)
+
+ status ||= "preparing"
else
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
@@ -237,8 +240,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- status = "preparing" if status.nil?
-
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
@@ -300,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
- @discussions = Note.discussions_from_notes(@notes)
+ @discussions = @notes.discussions
@noteable = @merge_request
# Get commits from repository
@@ -333,7 +334,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
- :state_event, :description, :task_num, label_ids: []
+ :state_event, :description, :task_num, :force_remove_source_branch,
+ label_ids: []
)
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index f7b6d137bde..da2892bfb3f 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -75,7 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to namespace_project_milestones_path }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 707a0d0e5c6..40b24d550e0 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -43,7 +43,7 @@ class Projects::NotesController < Projects::ApplicationController
end
respond_to do |format|
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
@@ -52,7 +52,7 @@ class Projects::NotesController < Projects::ApplicationController
note.update_attribute(:attachment, nil)
respond_to do |format|
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
@@ -96,7 +96,7 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_html(note)
- return unless note.for_diff_line?
+ return unless note.diff_note?
if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel"
@@ -120,7 +120,7 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_with_diff_html(note)
- return unless note.for_diff_line?
+ return unless note.diff_note?
render_to_string(
"projects/notes/_discussion",
@@ -158,7 +158,7 @@ class Projects::NotesController < Projects::ApplicationController
def note_params
params.require(:note).permit(
:note, :noteable, :noteable_id, :noteable_type, :project_id,
- :attachment, :line_code, :commit_id
+ :attachment, :line_code, :commit_id, :type
)
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
new file mode 100644
index 00000000000..b36081205d8
--- /dev/null
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -0,0 +1,59 @@
+class Projects::PipelinesController < Projects::ApplicationController
+ before_action :pipeline, except: [:index, :new, :create]
+ before_action :commit, only: [:show]
+ before_action :authorize_read_pipeline!
+ before_action :authorize_create_pipeline!, only: [:new, :create]
+ before_action :authorize_update_pipeline!, only: [:retry, :cancel]
+
+ def index
+ @scope = params[:scope]
+ all_pipelines = project.ci_commits
+ @pipelines_count = all_pipelines.count
+ @running_or_pending_count = all_pipelines.running_or_pending.count
+ @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
+ @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30)
+ end
+
+ def new
+ @pipeline = project.ci_commits.new(ref: @project.default_branch)
+ end
+
+ def create
+ @pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute
+ unless @pipeline.persisted?
+ render 'new'
+ return
+ end
+
+ redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
+ end
+
+ def show
+ end
+
+ def retry
+ pipeline.retry_failed
+
+ redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ end
+
+ def cancel
+ pipeline.cancel_running
+
+ redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ end
+
+ private
+
+ def create_params
+ params.require(:pipeline).permit(:ref)
+ end
+
+ def pipeline
+ @pipeline ||= project.ci_commits.find_by!(id: params[:id])
+ end
+
+ def commit
+ @commit ||= @pipeline.commit_data
+ end
+end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 33b2625c0ac..cdea5f0b776 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -55,7 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
format.html do
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
@@ -81,7 +81,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
- format.js { render nothing: true }
+ format.js { head :ok }
end
else
if current_user == @project.owner
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index e49259c34b6..efa7bf14d0f 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -39,7 +39,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
respond_to do |format|
format.html { redirect_to namespace_project_protected_branches_path }
- format.js { render nothing: true }
+ format.js { head :ok }
end
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 0dd2d6a99be..0b4fa572501 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -20,7 +20,7 @@ class Projects::RunnersController < Projects::ApplicationController
if @runner.update_attributes(runner_params)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
- redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ render 'edit'
end
end
@@ -64,6 +64,6 @@ class Projects::RunnersController < Projects::ApplicationController
end
def runner_params
- params.require(:runner).permit(:description, :tag_list, :active)
+ params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 00234654578..6f068729390 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -3,20 +3,44 @@ class Projects::VariablesController < Projects::ApplicationController
layout 'project_settings'
+ def index
+ @variable = Ci::Variable.new
+ end
+
def show
+ @variable = @project.variables.find(params[:id])
end
def update
- if project.update_attributes(project_params)
+ @variable = @project.variables.find(params[:id])
+
+ if @variable.update_attributes(project_params)
+ redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
+ else
+ render action: "show"
+ end
+ end
+
+ def create
+ @variable = Ci::Variable.new(project_params)
+
+ if @variable.valid? && @project.variables << @variable
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else
- render action: 'show'
+ render action: "index"
end
end
+ def destroy
+ @key = @project.variables.find(params[:id])
+ @key.destroy
+
+ redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully removed.'
+ end
+
private
def project_params
- params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
+ params.require(:variable).permit([:id, :key, :value, :_destroy])
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 0d6c32fabd2..4b404eb03fa 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -91,8 +91,8 @@ class Projects::WikisController < Projects::ApplicationController
def markdown_preview
text = params[:text]
- ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
- ext.analyze(text)
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext.analyze(text, author: current_user)
render json: {
body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3768efe142a..f94e2a84fa2 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -101,13 +101,7 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
format.html do
- if current_user
- @membership = @project.team.find_member(current_user.id)
-
- if @membership
- @notification_setting = current_user.notification_settings_for(@project)
- end
- end
+ @notification_setting = current_user.notification_settings_for(@project) if current_user
if @project.repository_exists?
if @project.empty_repo?
@@ -147,6 +141,7 @@ class ProjectsController < Projects::ApplicationController
@suggestions = {
emojis: AwardEmoji.urls,
issues: autocomplete.issues,
+ milestones: autocomplete.milestones,
mergerequests: autocomplete.merge_requests,
members: participants
}
@@ -202,8 +197,8 @@ class ProjectsController < Projects::ApplicationController
def markdown_preview
text = params[:text]
- ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
- ext.analyze(text)
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext.analyze(text, author: current_user)
render json: {
body: view_context.markdown(text),
@@ -235,7 +230,8 @@ class ProjectsController < Projects::ApplicationController
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
- :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
+ :issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
+ :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds,
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 352bff19383..75b78a49eab 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -37,8 +37,8 @@ class RegistrationsController < Devise::RegistrationsController
super
end
- def after_sign_up_path_for(_resource)
- users_almost_there_path
+ def after_sign_up_path_for(user)
+ user.confirmed? ? dashboard_projects_path : users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index c29f4609e93..d68c2a708e3 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,5 +1,6 @@
class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
+ include Devise::Controllers::Rememberable
include Recaptcha::ClientHelper
skip_before_action :check_2fa_requirement, only: [:destroy]
@@ -96,6 +97,7 @@ class SessionsController < Devise::SessionsController
# Remove any lingering user data from login
session.delete(:otp_user_id)
+ remember_me(user) if user_params[:remember_me] == '1'
sign_in(user) and return
else
flash.now[:alert] = 'Invalid two-factor code.'
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 2daceed039b..2a17c1f34db 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -10,7 +10,7 @@ class SnippetsController < ApplicationController
# Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
- skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw]
+ skip_before_action :authenticate_user!, only: [:index, :show, :raw]
layout 'snippets'
respond_to :html
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2ae180c8a12..a99632454d9 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -58,11 +58,22 @@ class UsersController < ApplicationController
end
end
+ def snippets
+ load_snippets
+
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json do
+ render json: {
+ html: view_to_html_string("snippets/_snippets", collection: @snippets)
+ }
+ end
+ end
+ end
+
def calendar
calendar = contributions_calendar
@timestamps = calendar.timestamps
- @starting_year = calendar.starting_year
- @starting_month = calendar.starting_month
render 'calendar', layout: false
end
@@ -116,6 +127,15 @@ class UsersController < ApplicationController
@groups = JoinedGroupsFinder.new(user).execute(current_user)
end
+ def load_snippets
+ @snippets = SnippetsFinder.new.execute(
+ current_user,
+ filter: :by_user,
+ user: user,
+ scope: params[:scope]
+ ).page(params[:page])
+ end
+
def projects_for_current_user
ProjectsFinder.new.execute(current_user)
end
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index 3b9a421b118..aa8f4c1d0e4 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -18,7 +18,7 @@ class GroupProjectsFinder < UnionFinder
projects = []
if current_user
- if @group.users.include?(current_user)
+ if @group.users.include?(current_user) || current_user.admin?
projects << @group.projects unless only_shared
projects << @group.shared_projects unless only_owned
else
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f00f3f709e9..7d8c56f4c22 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -250,12 +250,12 @@ class IssuableFinder
def by_milestone(items)
if milestones?
if filter_by_no_milestone?
- items = items.where(milestone_id: [-1, nil])
+ items = items.left_joins_milestones.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone?
- upcoming = Milestone.where(project_id: projects).upcoming
- items = items.joins(:milestone).where(milestones: { title: upcoming.try(:title) })
+ upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
+ items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
else
- items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
+ items = items.with_milestone(params[:milestone_title])
if projects
items = items.where(milestones: { project_id: projects })
@@ -271,7 +271,7 @@ class IssuableFinder
if filter_by_no_label?
items = items.without_label
else
- items = items.with_label(label_names)
+ items = items.with_label(label_names, params[:sort])
if projects
items = items.where(labels: { project_id: projects })
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index fa4c635f55c..c41be333537 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -10,7 +10,7 @@ class NotesFinder
notes =
case target_type
when "commit"
- project.notes.for_commit_id(target_id).not_inline
+ project.notes.for_commit_id(target_id).non_diff_notes
when "issue"
project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request"
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
new file mode 100644
index 00000000000..c19a795d467
--- /dev/null
+++ b/app/finders/pipelines_finder.rb
@@ -0,0 +1,38 @@
+class PipelinesFinder
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute(pipelines, scope)
+ case scope
+ when 'running'
+ pipelines.running_or_pending
+ when 'branches'
+ from_ids(pipelines, ids_for_ref(pipelines, branches))
+ when 'tags'
+ from_ids(pipelines, ids_for_ref(pipelines, tags))
+ else
+ pipelines
+ end
+ end
+
+ private
+
+ def ids_for_ref(pipelines, refs)
+ pipelines.where(ref: refs).group(:ref).select('max(id)')
+ end
+
+ def from_ids(pipelines, ids)
+ pipelines.unscoped.where(id: ids)
+ end
+
+ def branches
+ project.repository.branches.map(&:name)
+ end
+
+ def tags
+ project.repository.tags.map(&:name)
+ end
+end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 3ba27c40504..4bd46a76087 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -36,7 +36,7 @@ class TodosFinder
private
def action_id?
- action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i)
+ action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i)
end
def action_id
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3e0074da394..439b015b3b8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -110,8 +110,7 @@ module ApplicationHelper
]
# If reference is commit id - we should add it to branch/tag selectbox
- if(@ref && !options.flatten.include?(@ref) &&
- @ref =~ /\A[0-9a-zA-Z]{6,52}\z/)
+ if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
options << ['Commit', [@ref]]
end
@@ -263,6 +262,8 @@ module ApplicationHelper
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
+ issue_search: params[:issue_search],
+ label_name: params[:label_name]
}
options = exist_opts.merge(options)
@@ -273,16 +274,11 @@ module ApplicationHelper
end
end
- path = request.path
- path << "?#{options.to_param}"
- if add_label
- if params[:label_name].present? and params[:label_name].respond_to?('any?')
- params[:label_name].each do |label|
- path << "&label_name[]=#{label}"
- end
- end
- end
- path
+ params = options.compact
+
+ params.delete(:label_name) unless add_label
+
+ "#{request.path}?#{params.to_param}"
end
def outdated_browser?
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 914b0ef6042..03080d25931 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -60,4 +60,18 @@ module ApplicationSettingsHelper
end
end
end
+
+ def oauth_providers_checkboxes
+ button_based_providers.map do |source|
+ disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
+ css_class = 'btn'
+ css_class << ' active' unless disabled
+ checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
+
+ label_tag(checkbox_name, class: css_class) do
+ check_box_tag(checkbox_name, source, !disabled,
+ autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source)
+ end
+ end
+ end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index b4f80fd9b3e..b05fa0a14d6 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -38,6 +38,16 @@ module AuthHelper
auth_providers.reject { |provider| form_based_provider?(provider) }
end
+ def enabled_button_based_providers
+ disabled_providers = current_application_settings.disabled_oauth_sign_in_sources || []
+
+ button_based_providers.map(&:to_s) - disabled_providers
+ end
+
+ def button_based_providers_enabled?
+ enabled_button_based_providers.any?
+ end
+
def provider_image_tag(provider, size = 64)
label = label_for_provider(provider)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 93241b3afb7..cec2dc753fe 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -184,4 +184,14 @@ module BlobHelper
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
}
end
+
+ def gitignore_names
+ return @gitignore_names if defined?(@gitignore_names)
+
+ @gitignore_names = {
+ Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } },
+ # Note that the key here doesn't cover it really
+ Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } }
+ }
+ end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index a9047ede8c5..f742922d926 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -30,7 +30,7 @@ module ButtonHelper
content_tag :a, protocol,
class: klass,
- href: @project.http_url_to_repo,
+ href: project.http_url_to_repo,
data: {
html: true,
placement: 'right',
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 417050b4132..cfad17dcacf 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -38,19 +38,30 @@ module CiStatusHelper
icon(icon_name + ' fw')
end
- def render_ci_status(ci_commit, tooltip_placement: 'auto left')
- # TODO: split this method into
- # - render_commit_status
- # - render_pipeline_status
- link_to ci_icon_for_status(ci_commit.status),
- ci_status_path(ci_commit),
- class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
- title: "Build #{ci_label_for_status(ci_commit.status)}",
- data: { toggle: 'tooltip', placement: tooltip_placement }
+ def render_commit_status(commit, tooltip_placement: 'auto left')
+ project = commit.project
+ path = builds_namespace_project_commit_path(project.namespace, project, commit)
+ render_status_with_link('commit', commit.status, path, tooltip_placement)
+ end
+
+ def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
+ project = pipeline.project
+ path = namespace_project_pipeline_path(project.namespace, project, pipeline)
+ render_status_with_link('pipeline', pipeline.status, path, tooltip_placement)
end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
+
+ private
+
+ def render_status_with_link(type, status, path, tooltip_placement)
+ link_to ci_icon_for_status(status),
+ path,
+ class: "ci-status-link ci-status-icon-#{status.dasherize}",
+ title: "#{type.titleize}: #{ci_label_for_status(status)}",
+ data: { toggle: 'tooltip', placement: tooltip_placement }
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index b59c3982edd..d328f56c80c 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -123,13 +123,14 @@ module CommitsHelper
)
end
- def revert_commit_link(commit, continue_to_path, btn_class: nil)
+ def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user
- tooltip = "Revert this #{commit.change_type_title} in a new merge request"
+ tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip
if can_collaborate_with_project?
- link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
+ btn_class = "btn btn-grouped btn-close btn-#{btn_class}" unless btn_class.nil?
+ link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
@@ -140,17 +141,20 @@ module CommitsHelper
namespace_key: current_user.namespace.id,
continue: continue_params)
- link_to 'Revert', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
+ btn_class = "btn btn-grouped btn-close" unless btn_class.nil?
+
+ link_to 'Revert', fork_path, class: btn_class, method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip)
end
end
- def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil)
+ def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true)
return unless current_user
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
if can_collaborate_with_project?
- link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
+ btn_class = "btn btn-default btn-grouped btn-#{btn_class}" unless btn_class.nil?
+ link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
@@ -161,7 +165,8 @@ module CommitsHelper
namespace_key: current_user.namespace.id,
continue: continue_params)
- link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
+ btn_class = "btn btn-grouped btn-close" unless btn_class.nil?
+ link_to 'Cherry-pick', fork_path, class: "#{btn_class}", method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: (tooltip if has_tooltip)
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 9f73edb4553..cbe47176831 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -2,8 +2,8 @@ module DiffHelper
def mark_inline_diffs(old_line, new_line)
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
- marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs)
- marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs)
+ marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs, mode: :deletion)
+ marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs, mode: :addition)
[marked_old_line, marked_new_line]
end
@@ -39,11 +39,11 @@ module DiffHelper
end
def unfold_bottom_class(bottom)
- (bottom) ? 'js-unfold-bottom' : ''
+ bottom ? 'js-unfold-bottom' : ''
end
def unfold_class(unfold)
- (unfold) ? 'unfold js-unfold' : ''
+ unfold ? 'unfold js-unfold' : ''
end
def diff_line_content(line, line_type = nil)
@@ -55,22 +55,18 @@ module DiffHelper
end
end
- def line_comments
- @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
- end
-
- def organize_comments(type_left, type_right, line_code_left, line_code_right)
- comments_left = comments_right = nil
+ def organize_comments(left, right)
+ notes_left = notes_right = nil
- unless type_left.nil? && type_right == 'new'
- comments_left = line_comments[line_code_left]
+ unless left[:type].nil? && right[:type] == 'new'
+ notes_left = @grouped_diff_notes[left[:line_code]]
end
- unless type_left.nil? && type_right.nil?
- comments_right = line_comments[line_code_right]
+ unless left[:type].nil? && right[:type].nil?
+ notes_right = @grouped_diff_notes[right[:line_code]]
end
- [comments_left, comments_right]
+ [notes_left, notes_right]
end
def inline_diff_btn
@@ -96,8 +92,8 @@ module DiffHelper
].join(' ').html_safe
end
- def commit_for_diff(diff)
- if diff.deleted_file
+ def commit_for_diff(diff_file)
+ if diff_file.deleted_file
@base_commit || @commit.parent || @commit
else
@commit
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 41b5bd7be90..8466d0aa0ba 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -32,12 +32,6 @@ module EmailsHelper
nil
end
- def color_email_diff(diffcontent)
- formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
- lexer = Rouge::Lexers::Diff
- raw formatter.format(lexer.lex(diffcontent))
- end
-
def password_reset_token_valid_time
valid_hours = Devise.reset_password_within / 60 / 60
if valid_hours >= 24
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 592bad8ba24..bfedcb1c42b 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -3,7 +3,7 @@ module EventsHelper
author = event.author
if author
- link_to author.name, user_path(author.username), title: h(author.name)
+ link_to author.name, user_path(author.username), title: author.name
else
event.author_name
end
@@ -39,15 +39,6 @@ module EventsHelper
end
end
- def icon_for_event
- {
- EventFilter.push => 'upload',
- EventFilter.merged => 'check-square-o',
- EventFilter.comments => 'comments',
- EventFilter.team => 'user',
- }
- end
-
def event_preposition(event)
if event.push? || event.commented? || event.target
"at"
@@ -66,11 +57,7 @@ module EventsHelper
words << event.ref_name
words << "at"
elsif event.commented?
- if event.note_commit?
- words << event.note_short_commit_id
- else
- words << "##{truncate event.note_target_iid}"
- end
+ words << event.note_target_reference
words << "at"
elsif event.milestone?
words << "##{event.target_iid}" if event.target_iid
@@ -93,21 +80,12 @@ module EventsHelper
elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request)
- elsif event.note? && event.note_commit?
+ elsif event.note? && event.commit_note?
namespace_project_commit_url(event.project.namespace, event.project,
event.note_target)
elsif event.note?
if event.note_target
- if event.note_commit?
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_commit_id,
- anchor: dom_id(event.target))
- elsif event.note_project_snippet?
- namespace_project_snippet_path(event.project.namespace,
- event.project, event.note_target)
- else
- event_note_target_path(event)
- end
+ event_note_target_path(event)
end
elsif event.push?
push_event_feed_url(event)
@@ -143,42 +121,30 @@ module EventsHelper
end
def event_note_target_path(event)
- if event.note? && event.note_commit?
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_target)
+ if event.note? && event.commit_note?
+ namespace_project_commit_path(event.project.namespace,
+ event.project,
+ event.note_target,
+ anchor: dom_id(event.target))
+ elsif event.project_snippet_note?
+ namespace_project_snippet_path(event.project.namespace,
+ event.project,
+ event.note_target,
+ anchor: dom_id(event.target))
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
- anchor: dom_id(event.target))
+ anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
- if event.note_commit?
- link_to(
- namespace_project_commit_path(event.project.namespace, event.project,
- event.note_commit_id,
- anchor: dom_id(event.target), title: h(event.target_title)),
- class: "commit_short_id"
- ) do
- "#{event.note_target_type} #{event.note_short_commit_id}"
- end
- elsif event.note_project_snippet?
- link_to(namespace_project_snippet_path(event.project.namespace,
- event.project,
- event.note_target), title: h(event.project.name)) do
- "#{event.note_target_type} #{truncate event.note_target.to_reference}"
- end
- else
- link_to event_note_target_path(event) do
- "#{event.note_target_type} #{truncate event.note_target.to_reference}"
- end
+ link_to(event_note_target_path(event), title: event.target_title, class: 'has-tooltip') do
+ "#{event.note_target_type} #{event.note_target_reference}"
end
else
- content_tag :strong do
- "(deleted)"
- end
+ content_tag(:strong, '(deleted)')
end
end
@@ -193,28 +159,6 @@ module EventsHelper
"--broken encoding"
end
- def event_to_atom(xml, event)
- if event.visible_to_user?(current_user)
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
- event_summary = event_feed_summary(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link href: event_link
- xml.title truncate(event_title, length: 80)
- xml.updated event.created_at.xmlschema
- xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email))
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
-
- xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
- end
- end
- end
-
def event_row_class(event)
if event.body?
"event-block"
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 3a45205563e..0a1b48af219 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -13,7 +13,7 @@ module GitlabMarkdownHelper
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
- escaped_body = if body =~ /\A\<img/
+ escaped_body = if body.start_with?('<img')
body
else
escape_once(body)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f07eff3fb57..2ce2d4e694f 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -33,6 +33,10 @@ module GitlabRoutingHelper
namespace_project_builds_path(project.namespace, project, *args)
end
+ def project_container_registry_path(project, *args)
+ namespace_project_container_registry_index_path(project.namespace, project, *args)
+ end
+
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index b1f0a765bb9..4cac69c6795 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -31,7 +31,7 @@ module GroupsHelper
if group && group.avatar.present?
group.avatar.url
else
- 'no_group_avatar.png'
+ image_path('no_group_avatar.png')
end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 39474217286..fe84ee3de44 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -50,14 +50,10 @@ module IssuablesHelper
end
def user_dropdown_label(user_id, default_label)
+ return default_label if user_id.nil?
return "Unassigned" if user_id == "0"
- if @project
- member = @project.team.find_member(user_id)
- user = member.user if member
- else
- user = User.find_by(id: user_id)
- end
+ user = User.find_by(id: user_id)
if user
user.name
@@ -76,7 +72,7 @@ module IssuablesHelper
def issuable_meta(issuable, project, text)
output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
- output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
+ output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 198d39455d7..173bdbb8654 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -105,23 +105,6 @@ module IssuesHelper
return 'hidden' if issue.closed? == closed
end
- def issue_to_atom(xml, issue)
- xml.entry do
- xml.id namespace_project_issue_url(issue.project.namespace,
- issue.project, issue)
- xml.link href: namespace_project_issue_url(issue.project.namespace,
- issue.project, issue)
- xml.title truncate(issue.title, length: 80)
- xml.updated issue.created_at.xmlschema
- xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
- end
-
def merge_requests_sentence(merge_requests)
# Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
@@ -164,8 +147,8 @@ module IssuesHelper
def emoji_author_list(notes, current_user)
list = notes.map do |note|
- note.author == current_user ? "me" : note.author.name
- end
+ note.author == current_user ? "me" : note.author.name
+ end
list.join(", ")
end
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
new file mode 100644
index 00000000000..91dd91718dc
--- /dev/null
+++ b/app/helpers/javascript_helper.rb
@@ -0,0 +1,7 @@
+module JavascriptHelper
+ def page_specific_javascripts(js = nil)
+ @page_specific_javascripts = js unless js.nil?
+
+ @page_specific_javascripts
+ end
+end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 3aa41030453..f685e547537 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -43,4 +43,12 @@ module NavHelper
class_name += " with-horizontal-nav" if defined?(nav) && nav
class_name
end
+
+ def layout_nav_class
+ "page-with-layout-nav" if defined?(nav) && nav
+ end
+
+ def nav_control_class
+ "nav-control" if current_user
+ end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 95072b5373f..b401c8385be 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,7 +1,7 @@
module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
- (@noteable.class.name == note.noteable_type && !note.for_diff_line?)
+ @noteable.class.name == note.noteable_type && !note.diff_note?
end
def note_target_fields(note)
@@ -15,16 +15,6 @@ module NotesHelper
note.editable? && can?(current_user, :admin_note, note)
end
- def link_to_commit_diff_line_note(note)
- if note.for_commit_diff_line?
- link_to(
- "#{note.diff_file_name}:L#{note.diff_new_line}",
- namespace_project_commit_path(@project.namespace, @project,
- note.noteable, anchor: note.line_code)
- )
- end
- end
-
def noteable_json(noteable)
{
id: noteable.id,
@@ -35,7 +25,7 @@ module NotesHelper
end
def link_to_new_diff_note(line_code, line_type = nil)
- discussion_id = Note.build_discussion_id(
+ discussion_id = LegacyDiffNote.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
line_code
@@ -45,9 +35,10 @@ module NotesHelper
noteable_type: @comments_target[:noteable_type],
noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id],
+ line_type: line_type,
line_code: line_code,
- discussion_id: discussion_id,
- line_type: line_type
+ note_type: LegacyDiffNote.name,
+ discussion_id: discussion_id
}
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
@@ -57,18 +48,24 @@ module NotesHelper
end
end
- def link_to_reply_diff(note, line_type = nil)
+ def link_to_reply_discussion(note, line_type = nil)
return unless current_user
data = {
noteable_type: note.noteable_type,
noteable_id: note.noteable_id,
commit_id: note.commit_id,
- line_code: note.line_code,
discussion_id: note.discussion_id,
line_type: line_type
}
+ if note.diff_note?
+ data.merge!(
+ line_code: note.line_code,
+ note_type: LegacyDiffNote.name
+ )
+ end
+
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f17d02a7a3f..5e5d170a9f3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -124,11 +124,7 @@ module ProjectsHelper
end
def license_short_name(project)
- no_license_key = project.repository.license_key.nil? ||
- # Back-compat if cache contains 'no-license', can be removed in a few weeks
- project.repository.license_key == 'no-license'
-
- return 'LICENSE' if no_license_key
+ return 'LICENSE' if project.repository.license_key.nil?
license = Licensee::License.new(project.repository.license_key)
@@ -138,20 +134,28 @@ module ProjectsHelper
private
def get_project_nav_tabs(project, current_user)
- nav_tabs = [:home, :forks]
+ nav_tabs = [:home]
if !project.empty_repo? && can?(current_user, :download_code, project)
- nav_tabs << [:files, :commits, :network, :graphs]
+ nav_tabs << [:files, :commits, :network, :graphs, :forks]
end
if project.repo_exists? && can?(current_user, :read_merge_request, project)
nav_tabs << :merge_requests
end
+ if can?(current_user, :read_pipeline, project)
+ nav_tabs << :pipelines
+ end
+
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
+ if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
+ nav_tabs << :container_registry
+ end
+
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
@@ -205,7 +209,8 @@ module ProjectsHelper
end
def default_url_to_repo(project = @project)
- if default_clone_protocol == "ssh"
+ case default_clone_protocol
+ when 'ssh'
project.ssh_url_to_repo
else
project.http_url_to_repo
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 24c4c098c65..d2f94d4ae6f 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -59,7 +59,7 @@ module SearchHelper
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- ref = @ref || @project.repository.root_ref
+ ref = @ref || @project.repository.root_ref
[
{ category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index e951a87a212..bb395e37884 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -18,7 +18,7 @@ module SelectsHelper
first_user: first_user,
current_user: opts[:current_user] || false,
"push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
- author_id: opts[:author_id] || ''
+ author_id: opts[:author_id] || ''
}
}
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 96a83671009..563ddd2a511 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -95,7 +95,9 @@ module TabHelper
end
def project_tab_class
- return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
+ if controller.controller_path.start_with?('projects')
+ return 'active'
+ end
if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
"active"
@@ -112,7 +114,7 @@ module TabHelper
end
def profile_tab_class
- if controller.controller_path =~ /\Aprofiles/
+ if controller.controller_path.start_with?('profiles')
return 'active'
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 2f066682180..b4923fbb138 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -11,12 +11,15 @@ module TodosHelper
case todo.action
when Todo::ASSIGNED then 'assigned you'
when Todo::MENTIONED then 'mentioned you on'
+ when Todo::BUILD_FAILED then 'The build failed for your'
end
end
def todo_target_link(todo)
target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title }
+ link_to "#{target} #{todo.target_reference}", todo_target_path(todo),
+ class: 'has-tooltip',
+ title: todo.target.title
end
def todo_target_path(todo)
@@ -28,8 +31,21 @@ module TodosHelper
namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
todo.target, anchor: anchor)
else
- polymorphic_path([todo.project.namespace.becomes(Namespace),
- todo.project, todo.target], anchor: anchor)
+ path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
+
+ path.unshift(:builds) if todo.build_failed?
+
+ polymorphic_path(path, anchor: anchor)
+ end
+ end
+
+ def todo_target_state_pill(todo)
+ return unless show_todo_state?(todo)
+
+ content_tag(:span, nil, class: 'target-status') do
+ content_tag(:span, nil, class: "status-box status-box-#{todo.target.state.dasherize}") do
+ todo.target.state.capitalize
+ end
end
end
@@ -91,4 +107,10 @@ module TodosHelper
options_from_collection_for_select(types, 'name', 'title', params[:type])
end
+
+ private
+
+ def show_todo_state?(todo)
+ (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && ['closed', 'merged'].include?(todo.target.state)
+ end
end
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index b616add283a..415f6e12885 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,4 +1,6 @@
class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to
+
+ layout 'devise_mailer'
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 377c2999d6c..fdf1e9f5afc 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -59,20 +59,20 @@ module Emails
subject: subject("Project was moved"))
end
- def repository_push_email(project_id, recipient, opts = {})
+ def repository_push_email(project_id, opts = {})
@message =
- Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
+ Gitlab::Email::Message::RepositoryPush.new(self, project_id, opts)
# used in notify layout
@target_url = @message.target_url
- @project = Project.find project_id
+ @project = Project.find(project_id)
+ @diff_notes_disabled = true
add_project_headers
headers['X-GitLab-Author'] = @message.author_username
mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to,
- to: @message.recipient,
subject: @message.subject)
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 826e5f96fa1..1c663bdd521 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -10,6 +10,8 @@ class Notify < BaseMailer
include Emails::Builds
add_template_helper MergeRequestsHelper
+ add_template_helper DiffHelper
+ add_template_helper BlobHelper
add_template_helper EmailsHelper
def test_email(recipient_email, subject, body)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 6103a2947e2..44515550d9e 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -23,20 +23,41 @@ class Ability
end.concat(global_abilities(user))
end
+ # Given a list of users and a project this method returns the users that can
+ # read the given project.
+ def users_that_can_read_project(users, project)
+ if project.public?
+ users
+ else
+ users.select do |user|
+ if user.admin?
+ true
+ elsif project.internal? && !user.external?
+ true
+ elsif project.owner == user
+ true
+ elsif project.team.members.include?(user)
+ true
+ else
+ false
+ end
+ end
+ end
+ end
+
# List of possible abilities for anonymous user
def anonymous_abilities(user, subject)
- case true
- when subject.is_a?(PersonalSnippet)
+ if subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
- when subject.is_a?(ProjectSnippet)
+ elsif subject.is_a?(ProjectSnippet)
anonymous_project_snippet_abilities(subject)
- when subject.is_a?(CommitStatus)
+ elsif subject.is_a?(CommitStatus)
anonymous_commit_status_abilities(subject)
- when subject.is_a?(Project) || subject.respond_to?(:project)
+ elsif subject.is_a?(Project) || subject.respond_to?(:project)
anonymous_project_abilities(subject)
- when subject.is_a?(Group) || subject.respond_to?(:group)
+ elsif subject.is_a?(Group) || subject.respond_to?(:group)
anonymous_group_abilities(subject)
- when subject.is_a?(User)
+ elsif subject.is_a?(User)
anonymous_user_abilities
else
[]
@@ -60,7 +81,9 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
+ :read_pipeline,
:read_commit_status,
+ :read_container_image,
:download_code
]
@@ -203,6 +226,8 @@ class Ability
:admin_label,
:read_commit_status,
:read_build,
+ :read_container_image,
+ :read_pipeline,
]
end
@@ -214,9 +239,13 @@ class Ability
:update_commit_status,
:create_build,
:update_build,
+ :create_pipeline,
+ :update_pipeline,
:create_merge_request,
:create_wiki,
- :push_code
+ :push_code,
+ :create_container_image,
+ :update_container_image,
]
end
@@ -242,7 +271,9 @@ class Ability
:admin_wiki,
:admin_project,
:admin_commit_status,
- :admin_build
+ :admin_build,
+ :admin_container_image,
+ :admin_pipeline
]
end
@@ -285,6 +316,11 @@ class Ability
unless project.builds_enabled
rules += named_abilities('build')
+ rules += named_abilities('pipeline')
+ end
+
+ unless project.container_registry_enabled
+ rules += named_abilities('container_image')
end
rules
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index b61f5123127..b01a244032d 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class AbuseReport < ActiveRecord::Base
belongs_to :reporter, class_name: 'User'
belongs_to :user
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 4528760fefa..4cf8dd9a8ce 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,16 +1,3 @@
-# == 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 72ec91d2909..42f908aa344 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -1,63 +1,13 @@
-# == Schema Information
-#
-# Table name: application_settings
-#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# 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
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string
-# require_two_factor_authentication :boolean default(FALSE)
-# two_factor_grace_period :integer default(48)
-# metrics_enabled :boolean default(FALSE)
-# metrics_host :string default("localhost")
-# metrics_pool_size :integer default(16)
-# metrics_timeout :integer default(10)
-# metrics_method_call_threshold :integer default(10)
-# recaptcha_enabled :boolean default(FALSE)
-# 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
include TokenAuthenticatable
add_authentication_token_field :runners_registration_token
+ add_authentication_token_field :health_check_access_token
CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels
serialize :import_sources
+ serialize :disabled_oauth_sign_in_sources, Array
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
@@ -101,6 +51,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :container_registry_token_expire_delay,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -121,7 +75,18 @@ class ApplicationSetting < ActiveRecord::Base
end
end
+ validates_each :disabled_oauth_sign_in_sources do |record, attr, value|
+ unless value.nil?
+ value.each do |source|
+ unless Devise.omniauth_providers.include?(source.to_sym)
+ record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
+ end
+ end
+ end
+ end
+
before_save :ensure_runners_registration_token
+ before_save :ensure_health_check_access_token
after_commit do
Rails.cache.write(CACHE_KEY, self)
@@ -137,6 +102,10 @@ class ApplicationSetting < ActiveRecord::Base
Rails.cache.delete(CACHE_KEY)
end
+ def self.cached
+ Rails.cache.fetch(CACHE_KEY)
+ end
+
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -159,6 +128,9 @@ class ApplicationSetting < ActiveRecord::Base
recaptcha_enabled: false,
akismet_enabled: false,
repository_checks_enabled: true,
+ disabled_oauth_sign_in_sources: [],
+ send_user_confirmation_email: false,
+ container_registry_token_expire_delay: 5,
)
end
@@ -185,4 +157,8 @@ class ApplicationSetting < ActiveRecord::Base
def runners_registration_token
ensure_runners_registration_token!
end
+
+ def health_check_access_token
+ ensure_health_check_access_token!
+ end
end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 44b090260e7..967ffd46db0 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: audit_events
-#
-# id :integer not null, primary key
-# author_id :integer not null
-# type :string not null
-# entity_id :integer not null
-# entity_type :string not null
-# details :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 075ac733bfc..61498140f27 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string
-# font :string
-#
-
class BroadcastMessage < ActiveRecord::Base
include Sortable
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 4bc3a225e2c..64723ab6b4b 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1,40 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string
-# user_id :integer
-# type :string
-# target_url :string
-# description :string
-# artifacts_file :text
-# gl_project_id :integer
-# artifacts_metadata :text
-# erased_by_id :integer
-# erased_at :datetime
-#
-
module Ci
class Build < CommitStatus
belongs_to :runner, class_name: 'Ci::Runner'
@@ -90,6 +53,7 @@ module Ci
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.save
+ MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build
end
end
@@ -132,8 +96,12 @@ module Ci
end
def trace_html
- html = Ci::Ansi2html::convert(trace) if trace.present?
- html || ''
+ trace_with_state[:html] || ''
+ end
+
+ def trace_with_state(state = nil)
+ trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
+ trace_with_state || {}
end
def timeout
@@ -238,7 +206,7 @@ module Ci
end
def recreate_trace_dir
- unless Dir.exists?(dir_to_trace)
+ unless Dir.exist?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace)
end
end
@@ -318,14 +286,20 @@ module Ci
project.runners_token
end
- def valid_token? token
+ def valid_token?(token)
project.valid_runners_token? token
end
def can_be_served?(runner)
+ return false unless has_tags? || runner.run_untagged?
+
(tag_list - runner.tag_list).empty?
end
+ def has_tags?
+ tag_list.any?
+ end
+
def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
end
@@ -339,6 +313,7 @@ module Ci
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
+ project.running_or_pending_build_count(force: true)
end
def artifacts?
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 4ac4e0fb8b2..f22b573a94c 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -1,25 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_commits
-#
-# id :integer not null, primary key
-# project_id :integer
-# ref :string
-# sha :string
-# before_sha :string
-# push_data :text
-# created_at :datetime
-# updated_at :datetime
-# tag :boolean default(FALSE)
-# yaml_errors :text
-# committed_at :datetime
-# gl_project_id :integer
-# status :string
-# started_at :datetime
-# finished_at :datetime
-# duration :integer
-#
-
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
@@ -30,8 +8,6 @@ module Ci
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
- delegate :stages, to: :statuses
-
validates_presence_of :sha
validates_presence_of :status
validate :valid_commit_sha
@@ -44,7 +20,8 @@ module Ci
end
def self.stages
- CommitStatus.where(commit: all).stages
+ # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
+ CommitStatus.where(commit: pluck(:id)).stages
end
def project_id
@@ -89,6 +66,29 @@ module Ci
end
end
+ def cancelable?
+ builds.running_or_pending.any?
+ end
+
+ def cancel_running
+ builds.running_or_pending.each(&:cancel)
+ end
+
+ def retry_failed
+ builds.latest.failed.select(&:retryable?).each(&:retry)
+ end
+
+ def latest?
+ return false unless ref
+ commit = project.commit(ref)
+ return false unless commit
+ commit.sha == sha
+ end
+
+ def triggered?
+ trigger_requests.any?
+ end
+
def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index add59a08892..adb65292208 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -1,28 +1,10 @@
-# == Schema Information
-#
-# Table name: ci_runners
-#
-# id :integer not null, primary key
-# token :string
-# created_at :datetime
-# updated_at :datetime
-# description :string
-# contacted_at :datetime
-# active :boolean default(TRUE), not null
-# is_shared :boolean default(FALSE)
-# name :string
-# version :string
-# revision :string
-# platform :string
-# architecture :string
-#
-
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago
- AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
+ AVAILABLE_SCOPES = %w[specific shared active paused online]
+ FORM_EDITABLE = %i[description tag_list active run_untagged]
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -44,6 +26,8 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
+ validate :tag_constraints
+
acts_as_taggable
# Searches for runners matching the given query.
@@ -76,7 +60,7 @@ module Ci
end
def display_name
- return short_sha unless !description.blank?
+ return short_sha if description.blank?
description
end
@@ -114,5 +98,18 @@ module Ci
def short_sha
token[0...8] if token
end
+
+ def has_tags?
+ tag_list.any?
+ end
+
+ private
+
+ def tag_constraints
+ unless has_tags? || run_untagged?
+ errors.add(:tags_list,
+ 'can not be empty when runner is not allowed to pick untagged jobs')
+ end
+ end
end
end
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 7b16f207a26..4b44ffa886e 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_runner_projects
-#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
module Ci
class RunnerProject < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 4f3f4d79fac..a0b19b51a12 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_triggers
-#
-# id :integer not null, primary key
-# token :string
-# project_id :integer
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
module Ci
class Trigger < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 9973d2e5ade..872d5fb31de 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_trigger_requests
-#
-# id :integer not null, primary key
-# trigger_id :integer not null
-# variables :text
-# created_at :datetime
-# updated_at :datetime
-# commit_id :integer
-#
-
module Ci
class TriggerRequest < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 4229fe085a1..f8d5d4486fd 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-# id :integer not null, primary key
-# project_id :integer
-# key :string
-# value :text
-# encrypted_value :text
-# encrypted_value_salt :string
-# encrypted_value_iv :string
-# gl_project_id :integer
-#
-
module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
@@ -25,6 +11,9 @@ module Ci
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
- attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
+ attr_encrypted :value,
+ mode: :per_attribute_iv_and_salt,
+ key: Gitlab::Application.secrets.db_key_base,
+ algorithm: 'aes-256-cbc'
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 562c3ed15b2..f96c7cb34d0 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -8,7 +8,10 @@ class Commit
include StaticModel
attr_mentionable :safe_message, pipeline: :single_line
- participant :author, :committer, :notes
+
+ participant :author
+ participant :committer
+ participant :notes_with_associations
attr_accessor :project
@@ -194,6 +197,10 @@ class Commit
project.notes.for_commit_id(self.id)
end
+ def notes_with_associations
+ notes.includes(:author, :project)
+ end
+
def method_missing(m, *args, &block)
@raw.send(m, *args, &block)
end
@@ -219,7 +226,7 @@ class Commit
def revert_branch_name
"revert-#{short_id}"
end
-
+
def cherry_pick_branch_name
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
end
@@ -251,11 +258,13 @@ class Commit
end
def has_been_reverted?(current_user = nil, noteable = self)
- Gitlab::ReferenceExtractor.lazily do
- noteable.notes.system.flat_map do |note|
- note.all_references(current_user).commits
- end
- end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
+ ext = all_references(current_user)
+
+ noteable.notes_with_associations.system.each do |note|
+ note.all_references(current_user, extractor: ext)
+ end
+
+ ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) }
end
def change_type_title
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 51673897d98..4066958f67c 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -62,7 +62,7 @@ class CommitRange
def initialize(range_string, project)
@project = project
- range_string.strip!
+ range_string = range_string.strip
unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 1260c448de3..f774b6e0efb 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,40 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string
-# user_id :integer
-# 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
include Statuseable
@@ -51,7 +14,8 @@ class CommitStatus < ActiveRecord::Base
alias_attribute :author, :user
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
- scope :ordered, -> { order(:ref, :stage_idx, :name) }
+ scope :retried, -> { where.not(id: latest) }
+ scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
@@ -82,6 +46,10 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
+
+ after_transition any => :failed do |commit_status|
+ MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.commit.project, nil).execute(commit_status)
+ end
end
delegate :sha, :short_sha, to: :commit
@@ -91,13 +59,15 @@ class CommitStatus < ActiveRecord::Base
end
def self.stages
- order_by = 'max(stage_idx)'
- group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
+ # We group by stage name, but order stages by theirs' index
+ unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
end
def self.stages_status
- all.stages.inject({}) do |h, stage|
- h[stage] = all.where(stage: stage).status
+ # We execute subquery for each stage to calculate a stage status
+ statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
+ statuses.inject({}) do |h, k|
+ h[k.first] = k.last
h
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 2e4efc4e8d8..e86d5236abb 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -31,18 +31,21 @@ module Issuable
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
+ scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
- 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 :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
+ scope :order_milestone_due_desc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC') }
+ scope :order_milestone_due_asc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date 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,
@@ -56,11 +59,23 @@ module Issuable
prefix: true
attr_mentionable :title, pipeline: :single_line
- attr_mentionable :description, cache: true
- participant :author, :assignee, :notes_with_associations
+ attr_mentionable :description
+
+ participant :author
+ participant :assignee
+ participant :notes_with_associations
+
strip_attributes :title
acts_as_paranoid
+
+ after_save :update_assignee_cache_counts, if: :assignee_id_changed?
+
+ def update_assignee_cache_counts
+ # make sure we flush the cache for both the old *and* new assignee
+ User.find(assignee_id_was).update_cache_counts if assignee_id_was
+ assignee.update_cache_counts if assignee
+ end
end
module ClassMethods
@@ -123,13 +138,29 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
- def with_label(title)
- if title.is_a?(Array) && title.count > 1
- joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
+ def with_label(title, sort = nil)
+ if title.is_a?(Array) && title.size > 1
+ joins(:labels).where(labels: { title: title }).group(*grouping_columns(sort)).having("COUNT(DISTINCT labels.title) = #{title.size}")
else
joins(:labels).where(labels: { title: title })
end
end
+
+ # Includes table keys in group by clause when sorting
+ # preventing errors in postgres
+ #
+ # Returns an array of arel columns
+ def grouping_columns(sort)
+ grouping_columns = [arel_table[:id]]
+
+ if ["milestone_due_desc", "milestone_due_asc"].include?(sort)
+ milestone_table = Milestone.arel_table
+ grouping_columns << milestone_table[:id]
+ grouping_columns << milestone_table[:due_date]
+ end
+
+ grouping_columns
+ end
end
def today?
@@ -160,6 +191,10 @@ module Issuable
notes.awards.where(note: "thumbsup").count
end
+ def user_notes_count
+ notes.user.count
+ end
+
def subscribed_without_subscriptions?(user)
participants(user).include?(user)
end
@@ -178,6 +213,10 @@ module Issuable
hook_data
end
+ def labels_array
+ labels.to_a
+ end
+
def label_names
labels.order('title ASC').pluck(:title)
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 98f71ae8cb0..f00b5b8497c 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -23,7 +23,7 @@ module Mentionable
included do
if self < Participable
- participant ->(current_user) { mentioned_users(current_user) }
+ participant -> (user, ext) { all_references(user, extractor: ext) }
end
end
@@ -43,23 +43,22 @@ module Mentionable
self
end
- def all_references(current_user = self.author, text = nil)
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author)
+ def all_references(current_user = nil, text = nil, extractor: nil)
+ extractor ||= Gitlab::ReferenceExtractor.
+ new(project, current_user || author)
if text
- ext.analyze(text)
+ extractor.analyze(text, author: author)
else
self.class.mentionable_attrs.each do |attr, options|
- text = send(attr)
+ text = __send__(attr)
+ options = options.merge(cache_key: [self, attr], author: author)
- context = options.dup
- context[:cache_key] = [self, attr] if context.delete(:cache) && self.persisted?
-
- ext.analyze(text, context)
+ extractor.analyze(text, options)
end
end
- ext
+ extractor
end
def mentioned_users(current_user = nil)
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index fc6f83b918b..9056722f45e 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -3,8 +3,6 @@
# Contains functionality related to objects that can have participants, such as
# an author, an assignee and people mentioned in its description or comments.
#
-# Used by Issue, Note, MergeRequest, Snippet and Commit.
-#
# Usage:
#
# class Issue < ActiveRecord::Base
@@ -12,22 +10,36 @@
#
# # ...
#
-# participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
+# participant :author
+# participant :assignee
+# participant :notes
+#
+# participant -> (current_user, ext) do
+# ext.analyze('...')
+# end
# end
#
# issue = Issue.last
# users = issue.participants
-# # `users` will contain the issue's author, its assignee,
-# # all users returned by its #mentioned_users method,
-# # as well as all participants to all of the issue's notes,
-# # since Note implements Participable as well.
-#
module Participable
extend ActiveSupport::Concern
module ClassMethods
- def participant(*attrs)
- participant_attrs.concat(attrs)
+ # Adds a list of participant attributes. Attributes can either be symbols or
+ # Procs.
+ #
+ # When using a Proc instead of a Symbol the Proc will be given two
+ # arguments:
+ #
+ # 1. The current user (as an instance of User)
+ # 2. An instance of `Gitlab::ReferenceExtractor`
+ #
+ # It is expected that a Proc populates the given reference extractor
+ # instance with data. The return value of the Proc is ignored.
+ #
+ # attr - The name of the attribute or a Proc
+ def participant(attr)
+ participant_attrs << attr
end
def participant_attrs
@@ -35,42 +47,42 @@ module Participable
end
end
- # Be aware that this method makes a lot of sql queries.
- # Save result into variable if you are going to reuse it inside same request
- def participants(current_user = self.author)
- participants =
- Gitlab::ReferenceExtractor.lazily do
- self.class.participant_attrs.flat_map do |attr|
- value =
- if attr.respond_to?(:call)
- instance_exec(current_user, &attr)
- else
- send(attr)
- end
+ # Returns the users participating in a discussion.
+ #
+ # This method processes attributes of objects in breadth-first order.
+ #
+ # Returns an Array of User instances.
+ def participants(current_user = nil)
+ current_user ||= author
+ ext = Gitlab::ReferenceExtractor.new(project, current_user)
+ participants = Set.new
+ process = [self]
- participants_for(value, current_user)
- end.compact.uniq
- end
+ until process.empty?
+ source = process.pop
- unless Gitlab::ReferenceExtractor.lazy?
- participants.select! do |user|
- user.can?(:read_project, project)
+ case source
+ when User
+ participants << source
+ when Participable
+ source.class.participant_attrs.each do |attr|
+ if attr.respond_to?(:call)
+ source.instance_exec(current_user, ext, &attr)
+ else
+ process << source.__send__(attr)
+ end
+ end
+ when Enumerable, ActiveRecord::Relation
+ # This uses reverse_each so we can use "pop" to get the next value to
+ # process (in order). Using unshift instead of pop would require
+ # moving all Array values one index to the left (which can be
+ # expensive).
+ source.reverse_each { |obj| process << obj }
end
end
- participants
- end
-
- private
+ participants.merge(ext.users)
- def participants_for(value, current_user = nil)
- case value
- when User, Banzai::LazyReference
- [value]
- when Enumerable, ActiveRecord::Relation
- value.flat_map { |v| participants_for(v, current_user) }
- when Participable
- value.participants(current_user)
- end
+ Ability.users_that_can_read_project(participants.to_a, project)
end
end
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index d5a881b2445..083257f1005 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -36,6 +36,12 @@ module Subscribable
update(subscribed: !subscribed?(user))
end
+ def subscribe(user)
+ subscriptions.
+ find_or_initialize_by(user_id: user.id).
+ update(subscribed: true)
+ end
+
def unsubscribe(user)
subscriptions.
find_or_initialize_by(user_id: user.id).
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 43cf625f770..2c525d4cd7a 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string
-# type :string
-# fingerprint :string
-# public :boolean default(FALSE), not null
-#
-
class DeployKey < Key
has_many :deploy_keys_projects, dependent: :destroy
has_many :projects, through: :deploy_keys_projects
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 18db521741f..ae8486bd9ac 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: deploy_keys_projects
-#
-# id :integer not null, primary key
-# deploy_key_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class DeployKeysProject < ActiveRecord::Base
belongs_to :project
belongs_to :deploy_key
diff --git a/app/models/email.rb b/app/models/email.rb
index eae2472f337..32a412ab878 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: emails
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# email :string not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class Email < ActiveRecord::Base
include Sortable
diff --git a/app/models/event.rb b/app/models/event.rb
index 25c7c3e6dc7..716039fb54b 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: events
-#
-# id :integer not null, primary key
-# target_type :string
-# target_id :integer
-# title :string
-# data :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# action :integer
-# author_id :integer
-#
-
class Event < ActiveRecord::Base
include Sortable
default_scope { where.not(author_id: nil) }
@@ -96,7 +80,7 @@ class Event < ActiveRecord::Base
end
def target_title
- target.title if target && target.respond_to?(:title)
+ target.try(:title)
end
def created?
@@ -282,28 +266,20 @@ class Event < ActiveRecord::Base
branch? && project.default_branch != branch_name
end
- def note_commit_id
- target.commit_id
- end
-
def target_iid
target.respond_to?(:iid) ? target.iid : target_id
end
- def note_short_commit_id
- Commit.truncate_sha(note_commit_id)
- end
-
- def note_commit?
- target.noteable_type == "Commit"
+ def commit_note?
+ target.for_commit?
end
def issue_note?
- note? && target && target.noteable_type == "Issue"
+ note? && target && target.for_issue?
end
- def note_project_snippet?
- target.noteable_type == "Snippet"
+ def project_snippet_note?
+ target.for_snippet?
end
def note_target
@@ -311,19 +287,22 @@ class Event < ActiveRecord::Base
end
def note_target_id
- if note_commit?
+ if commit_note?
target.commit_id
else
target.noteable_id.to_s
end
end
- def note_target_iid
- if note_target.respond_to?(:iid)
- note_target.iid
+ def note_target_reference
+ return unless note_target
+
+ # Commit#to_reference returns the full SHA, but we want the short one here
+ if commit_note?
+ note_target.short_id
else
- note_target_id
- end.to_s
+ note_target.to_reference
+ end
end
def note_target_type
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index 9b0c6263a96..9803bae0bee 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class ForkedProjectLink < ActiveRecord::Base
belongs_to :forked_to_project, class_name: Project
belongs_to :forked_from_project, class_name: Project
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index d4afd8cbe84..fa54e3540d0 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -1,40 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string
-# user_id :integer
-# 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
before_validation :set_default_values
diff --git a/app/models/group.rb b/app/models/group.rb
index cff76877958..aec92e335e6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -1,20 +1,3 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# 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'
class Group < Namespace
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 2b8f34a0568..ba42a8eeb70 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,25 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# 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
belongs_to :project
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 0e176de5ef8..eef24052a06 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -1,25 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# 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
belongs_to :service
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index ad508cbbcb8..777bad1e724 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,25 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# 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
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 8e58c9583ab..8b87b6c3d64 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,25 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# 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
include Sortable
include HTTParty
@@ -60,7 +38,7 @@ class WebHook < ActiveRecord::Base
basic_auth: auth)
end
- [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+ [response.code, response.to_s]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
[false, e.to_s]
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ef4d5f99091..3bacc450e6e 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: identities
-#
-# id :integer not null, primary key
-# extern_uid :string
-# provider :string
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
class Identity < ActiveRecord::Base
include Sortable
include CaseSensitivity
diff --git a/app/models/issue.rb b/app/models/issue.rb
index abaa509707c..bd0fbc96d18 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: issues
-#
-# id :integer not null, primary key
-# title :string
-# assignee_id :integer
-# author_id :integer
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# position :integer default(0)
-# branch_name :string
-# description :text
-# milestone_id :integer
-# 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'
class Issue < ActiveRecord::Base
@@ -119,14 +95,13 @@ class Issue < ActiveRecord::Base
end
def referenced_merge_requests(current_user = nil)
- @referenced_merge_requests ||= {}
- @referenced_merge_requests[current_user] ||= begin
- Gitlab::ReferenceExtractor.lazily do
- [self, *notes].flat_map do |note|
- note.all_references(current_user).merge_requests
- end
- end.sort_by(&:iid).uniq
+ ext = all_references(current_user)
+
+ notes_with_associations.each do |object|
+ object.all_references(current_user, extractor: ext)
end
+
+ ext.merge_requests.sort_by(&:iid)
end
# All branches containing the current issue's ID, except for
@@ -163,9 +138,13 @@ class Issue < ActiveRecord::Base
def closed_by_merge_requests(current_user = nil)
return [] unless open?
- notes.system.flat_map do |note|
- note.all_references(current_user).merge_requests
- end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
+ ext = all_references(current_user)
+
+ notes.system.each do |note|
+ note.all_references(current_user, extractor: ext)
+ end
+
+ ext.merge_requests.select { |mr| mr.open? && mr.closes_issue?(self) }
end
def moved?
diff --git a/app/models/key.rb b/app/models/key.rb
index b2b57849f8a..0532e84f47d 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string
-# type :string
-# fingerprint :string
-# public :boolean default(FALSE), not null
-#
-
require 'digest/md5'
class Key < ActiveRecord::Base
@@ -41,7 +26,7 @@ class Key < ActiveRecord::Base
end
def publishable_key
- #Removes anything beyond the keytype and key itself
+ # Removes anything beyond the keytype and key itself
self.key.split[0..1].join(' ')
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 9a22398d952..e5ad11983be 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: labels
-#
-# 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
include Referable
include Subscribable
@@ -117,6 +103,10 @@ class Label < ActiveRecord::Base
LabelsHelper::text_color_for_bg(self.color)
end
+ def title=(value)
+ write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ end
+
private
def label_format_reference(format = :id)
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 7b8e872b6dd..47bd6eaf35f 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string
-# created_at :datetime
-# updated_at :datetime
-#
-
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
new file mode 100644
index 00000000000..bbefc911b29
--- /dev/null
+++ b/app/models/legacy_diff_note.rb
@@ -0,0 +1,157 @@
+class LegacyDiffNote < Note
+ serialize :st_diff
+
+ validates :line_code, presence: true, line_code: true
+
+ before_create :set_diff
+
+ class << self
+ def build_discussion_id(noteable_type, noteable_id, line_code, active = true)
+ [super(noteable_type, noteable_id), line_code, active].join("-")
+ end
+ end
+
+ def diff_note?
+ true
+ end
+
+ def legacy_diff_note?
+ true
+ end
+
+ def discussion_id
+ @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
+ end
+
+ def diff_file_hash
+ line_code.split('_')[0] if line_code
+ end
+
+ def diff_old_line
+ line_code.split('_')[1].to_i if line_code
+ end
+
+ def diff_new_line
+ line_code.split('_')[2].to_i if line_code
+ end
+
+ def diff
+ @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
+ end
+
+ def diff_file_path
+ diff.new_path.presence || diff.old_path
+ end
+
+ def diff_lines
+ @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
+ end
+
+ def diff_line
+ @diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
+ end
+
+ def diff_line_text
+ diff_line.try(:text)
+ end
+
+ def diff_line_type
+ diff_line.try(:type)
+ end
+
+ def highlighted_diff_lines
+ Gitlab::Diff::Highlight.new(diff_lines).highlight
+ end
+
+ def truncated_diff_lines
+ max_number_of_lines = 16
+ prev_match_line = nil
+ prev_lines = []
+
+ highlighted_diff_lines.each do |line|
+ if line.type == "match"
+ prev_lines.clear
+ prev_match_line = line
+ else
+ prev_lines << line
+
+ break if generate_line_code(line) == self.line_code
+
+ prev_lines.shift if prev_lines.length >= max_number_of_lines
+ end
+ end
+
+ prev_lines
+ end
+
+ # Check if this note is part of an "active" discussion
+ #
+ # This will always return true for anything except MergeRequest noteables,
+ # which have special logic.
+ #
+ # If the note's current diff cannot be matched in the MergeRequest's current
+ # diff, it's considered inactive.
+ def active?
+ return @active if defined?(@active)
+ return true if for_commit?
+ return true unless self.diff
+ return false unless noteable
+
+ noteable_diff = find_noteable_diff
+
+ if noteable_diff
+ parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
+
+ @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
+ else
+ @active = false
+ end
+
+ @active
+ end
+
+ private
+
+ def find_diff
+ return nil unless noteable
+ return @diff if defined?(@diff)
+
+ @diff = noteable.diffs(Commit.max_diff_options).find do |d|
+ d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
+ end
+ end
+
+ def set_diff
+ # First lets find notes with same diff
+ # before iterating over all mr diffs
+ diff = diff_for_line_code unless for_merge_request?
+ diff ||= find_diff
+
+ self.st_diff = diff.to_hash if diff
+ end
+
+ def diff_for_line_code
+ attributes = {
+ noteable_type: noteable_type,
+ line_code: line_code
+ }
+
+ if for_commit?
+ attributes[:commit_id] = commit_id
+ else
+ attributes[:noteable_id] = noteable_id
+ end
+
+ self.class.where(attributes).last.try(:diff)
+ end
+
+ def generate_line_code(line)
+ Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
+ end
+
+ # Find the diff on noteable that matches our own
+ def find_noteable_diff
+ diffs = noteable.diffs(Commit.max_diff_options)
+ diffs.find { |d| d.new_path == self.diff.new_path }
+ end
+end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 927e764af92..18657c3e1c8 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects
-#
-# id :integer not null, primary key
-# oid :string not null
-# size :integer not null
-# created_at :datetime
-# updated_at :datetime
-# file :string
-#
-
class LfsObject < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy
has_many :projects, through: :lfs_objects_projects
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 890736bfc80..0fd5f089db9 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects_projects
-#
-# id :integer not null, primary key
-# lfs_object_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class LfsObjectsProject < ActiveRecord::Base
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/member.rb b/app/models/member.rb
index cca82da89f1..d3060f07fc0 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string not null
-# user_id :integer
-# notification_level :integer not null
-# type :string
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string
-# invite_token :string
-# invite_accepted_at :datetime
-#
-
class Member < ActiveRecord::Base
include Sortable
include Gitlab::Access
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index a48c1943e6f..f63a0debf1a 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string not null
-# user_id :integer
-# notification_level :integer not null
-# type :string
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string
-# invite_token :string
-# invite_accepted_at :datetime
-#
-
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 143350a0b55..46955b430f3 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string not null
-# user_id :integer
-# notification_level :integer not null
-# type :string
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string
-# invite_token :string
-# invite_accepted_at :datetime
-#
-
class ProjectMember < Member
SOURCE_TYPE = 'Project'
@@ -24,7 +5,6 @@ class ProjectMember < Member
belongs_to :project, class_name: 'Project', foreign_key: 'source_id'
-
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
validates_format_of :source_type, with: /\AProject\z/
@@ -34,6 +14,8 @@ class ProjectMember < Member
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
+ before_destroy :delete_member_todos
+
class << self
# Add users to project teams with passed access option
@@ -121,6 +103,10 @@ class ProjectMember < Member
private
+ def delete_member_todos
+ user.todos.where(project_id: source_id).destroy_all if user
+ end
+
def send_invite
notification_service.invite_project_member(self, @raw_invite_token)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 4175e1e5fba..722c258244c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1,33 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string not null
-# source_branch :string not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# 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
-# 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
include InternalId
include Issuable
@@ -56,6 +26,10 @@ class MergeRequest < ActiveRecord::Base
# when creating new merge request
attr_accessor :can_be_created, :compare_commits, :compare
+ # Temporary fields to store target_sha, and base_sha to
+ # compare when importing pull requests from GitHub
+ attr_accessor :base_target_sha, :head_source_sha
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -312,6 +286,18 @@ class MergeRequest < ActiveRecord::Base
last_commit == source_project.commit(source_branch)
end
+ def should_remove_source_branch?
+ merge_params['should_remove_source_branch'].present?
+ end
+
+ def force_remove_source_branch?
+ merge_params['force_remove_source_branch'].present?
+ end
+
+ def remove_source_branch?
+ should_remove_source_branch? || force_remove_source_branch?
+ end
+
def mr_and_commit_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
@@ -452,7 +438,10 @@ class MergeRequest < ActiveRecord::Base
self.merge_when_build_succeeds = false
self.merge_user = nil
- self.merge_params = nil
+ if merge_params
+ merge_params.delete('should_remove_source_branch')
+ merge_params.delete('commit_message')
+ end
self.save
end
@@ -520,10 +509,14 @@ class MergeRequest < ActiveRecord::Base
end
def target_sha
- @target_sha ||= target_project.repository.commit(target_branch).try(:sha)
+ return @base_target_sha if defined?(@base_target_sha)
+
+ target_project.repository.commit(target_branch).try(:sha)
end
def source_sha
+ return @head_source_sha if defined?(@head_source_sha)
+
last_commit.try(:sha) || source_tip.try(:sha)
end
@@ -544,7 +537,7 @@ class MergeRequest < ActiveRecord::Base
end
def ref_is_fetched?
- File.exists?(File.join(project.repository.path_to_repo, ref_path))
+ File.exist?(File.join(project.repository.path_to_repo, ref_path))
end
def ensure_ref_fetched
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 8951e92a0b8..7d5103748f5 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_request_diffs
-#
-# id :integer not null, primary key
-# 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
include Sortable
@@ -21,7 +6,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
- delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
+ delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do
state :collected
@@ -53,8 +38,8 @@ class MergeRequestDiff < ActiveRecord::Base
@diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new(
self.repository.raw_repository,
- self.target_branch,
- self.source_sha,
+ self.base,
+ self.head,
)
compare.diffs(options)
end
@@ -113,9 +98,7 @@ class MergeRequestDiff < ActiveRecord::Base
commits = compare.commits
if commits.present?
- commits = Commit.decorate(commits, merge_request.source_project).
- sort_by(&:created_at).
- reverse
+ commits = Commit.decorate(commits, merge_request.source_project).reverse
end
commits
@@ -159,7 +142,7 @@ class MergeRequestDiff < ActiveRecord::Base
self.st_diffs = new_diffs
- self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch)
+ self.base_commit_sha = self.repository.merge_base(self.head, self.base)
self.save
end
@@ -175,10 +158,24 @@ class MergeRequestDiff < ActiveRecord::Base
end
def source_sha
+ return head_source_sha if head_source_sha.present?
+
source_commit = merge_request.source_project.commit(source_branch)
source_commit.try(:sha)
end
+ def target_sha
+ merge_request.target_sha
+ end
+
+ def base
+ self.target_sha || self.target_branch
+ end
+
+ def head
+ self.source_sha
+ end
+
def compare
@compare ||=
begin
@@ -187,8 +184,8 @@ class MergeRequestDiff < ActiveRecord::Base
Gitlab::Git::Compare.new(
self.repository.raw_repository,
- self.target_branch,
- self.source_sha
+ self.base,
+ self.head
)
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 5ee8a965ad8..e0c8454a998 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: milestones
-#
-# id :integer not null, primary key
-# title :string not null
-# project_id :integer not null
-# description :text
-# due_date :date
-# created_at :datetime
-# updated_at :datetime
-# state :string
-# iid :integer
-#
-
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
@@ -74,25 +59,67 @@ class Milestone < ActiveRecord::Base
end
end
+ def self.reference_prefix
+ '%'
+ end
+
def self.reference_pattern
- nil
+ # NOTE: The iid pattern only matches when all characters on the expression
+ # are digits, so it will match %2 but not %2.1 because that's probably a
+ # milestone name and we want it to be matched as such.
+ @reference_pattern ||= %r{
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}
+ (?:
+ (?<milestone_iid>
+ \d+(?!\S\w)\b # Integer-based milestone iid, or
+ ) |
+ (?<milestone_name>
+ [^"\s]+\b | # String-based single-word milestone title, or
+ "[^"]+" # String-based multi-word milestone surrounded in quotes
+ )
+ )
+ }x
end
def self.link_reference_pattern
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
- def self.upcoming
- self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
- end
+ def self.upcoming_ids_by_projects(projects)
+ rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now)
- def to_reference(from_project = nil)
- escaped_title = self.title.gsub("]", "\\]")
+ if Gitlab::Database.postgresql?
+ rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
+ else
+ rel.
+ group(:project_id).
+ having('due_date = MIN(due_date)').
+ pluck(:id, :project_id, :due_date).
+ map(&:first)
+ end
+ end
- h = Gitlab::Routing.url_helpers
- url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
+ ##
+ # Returns the String necessary to reference this Milestone in Markdown
+ #
+ # format - Symbol format to use (default: :iid, optional: :name)
+ #
+ # Examples:
+ #
+ # Milestone.first.to_reference # => "%1"
+ # Milestone.first.to_reference(format: :name) # => "%\"goal\""
+ # Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1"
+ #
+ def to_reference(from_project = nil, format: :iid)
+ format_reference = milestone_format_reference(format)
+ reference = "#{self.class.reference_prefix}#{format_reference}"
- "[#{escaped_title}](#{url})"
+ if cross_project_reference?(from_project)
+ project.to_reference + reference
+ else
+ reference
+ end
end
def reference_link_text(from_project = nil)
@@ -129,6 +156,10 @@ class Milestone < ActiveRecord::Base
nil
end
+ def title=(value)
+ write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ end
+
# Sorts the issues for the given IDs.
#
# This method runs a single SQL query using a CASE statement to update the
@@ -160,4 +191,16 @@ class Milestone < ActiveRecord::Base
issues.where(id: ids).
update_all(["position = CASE #{conditions} ELSE position END", *pairs])
end
+
+ private
+
+ def milestone_format_reference(format = :iid)
+ raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
+
+ if format == :name && !name.include?('"')
+ %("#{name}")
+ else
+ iid
+ end
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 741e912171d..da19462f265 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,20 +1,3 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# 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
include Sortable
include Gitlab::ShellAdapter
@@ -127,6 +110,10 @@ class Namespace < ActiveRecord::Base
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(path_was)
+ if any_project_has_container_registry_tags?
+ raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry')
+ end
+
if gitlab_shell.mv_namespace(path_was, path)
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
@@ -148,6 +135,10 @@ class Namespace < ActiveRecord::Base
end
end
+ def any_project_has_container_registry_tags?
+ projects.any?(&:has_container_registry_tags?)
+ end
+
def send_update_instructions
projects.each do |project|
project.send_move_instructions("#{path_was}/#{project.path}")
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index f4e90125373..a2aee2f925b 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -22,9 +22,16 @@ module Network
def collect_notes
h = Hash.new(0)
- @project.notes.where('noteable_type = ?' ,"Commit").group('notes.commit_id').select('notes.commit_id, count(notes.id) as note_count').each do |item|
- h[item.commit_id] = item.note_count.to_i
- end
+
+ @project
+ .notes
+ .where('noteable_type = ?', 'Commit')
+ .group('notes.commit_id')
+ .select('notes.commit_id, count(notes.id) as note_count')
+ .each do |item|
+ h[item.commit_id] = item.note_count.to_i
+ end
+
h
end
@@ -89,7 +96,7 @@ module Network
end
end
- if self.class.max_count / 2 < offset then
+ if self.class.max_count / 2 < offset
# get max index that commit is displayed in the center.
offset - self.class.max_count / 2
else
@@ -130,7 +137,7 @@ module Network
commit.parents(@map).each do |parent|
range = commit.time..parent.time
- space = if commit.space >= parent.space then
+ space = if commit.space >= parent.space
find_free_parent_space(range, parent.space, -1, commit.space)
else
find_free_parent_space(range, commit.space, -1, parent.space)
@@ -144,7 +151,7 @@ module Network
end
def find_free_parent_space(range, space_base, space_step, space_default)
- if is_overlap?(range, space_default) then
+ if is_overlap?(range, space_default)
find_free_space(range, space_step, space_base, space_default)
else
space_default
@@ -155,9 +162,9 @@ module Network
range.each do |i|
if i != range.first &&
i != range.last &&
- @commits[i].spaces.include?(overlap_space) then
+ @commits[i].spaces.include?(overlap_space)
- return true;
+ return true
end
end
@@ -198,7 +205,7 @@ module Network
# Visit branching chains
leaves.each do |l|
parents = l.parents(@map).select{|p| p.space.zero?}
- for p in parents
+ parents.each do |p|
place_chain(p, l.time)
end
end
@@ -216,7 +223,7 @@ module Network
end
def mark_reserved(time_range, space)
- for day in time_range
+ time_range.each do |day|
@reserved[day].push(space)
end
end
@@ -225,15 +232,15 @@ module Network
space_default ||= space_base
reserved = []
- for day in time_range
+ time_range.each do |day|
reserved.push(*@reserved[day])
end
reserved.uniq!
space = space_default
- while reserved.include?(space) do
+ while reserved.include?(space)
space += space_step
- if space < space_base then
+ if space < space_base
space_step *= -1
space = space_base + space_step
end
@@ -253,7 +260,7 @@ module Network
leaves = []
leaves.push(commit) if commit.space.zero?
- while true
+ loop do
return leaves if commit.parents(@map).count.zero?
commit = commit.parents(@map).first
diff --git a/app/models/note.rb b/app/models/note.rb
index deee2b9e885..c21981ead84 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -1,34 +1,12 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string
-# line_code :string
-# commit_id :string
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-# is_award :boolean default(FALSE), not null
-#
-
-require 'carrierwave/orm/activerecord'
-
class Note < ActiveRecord::Base
+ extend ActiveModel::Naming
include Gitlab::CurrentSettings
include Participable
include Mentionable
default_value_for :system, false
- attr_mentionable :note, cache: true, pipeline: :note
+ attr_mentionable :note, pipeline: :note
participant :author
belongs_to :project
@@ -41,29 +19,33 @@ class Note < ActiveRecord::Base
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
+ delegate :title, to: :noteable, allow_nil: true
before_validation :set_award!
- before_validation :clear_blank_line_code!
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
- validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
- validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
- validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
+ validates :noteable_type, presence: true
+ validates :noteable_id, presence: true, unless: :for_commit?
+ validates :commit_id, presence: true, if: :for_commit?
validates :author, presence: true
+ validate unless: :for_commit? do |note|
+ unless note.noteable.try(:project) == note.project
+ errors.add(:invalid_project, 'Note and noteable project mismatch')
+ end
+ end
+
mount_uploader :attachment, AttachmentUploader
# Scopes
scope :awards, ->{ where(is_award: true) }
scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
- scope :inline, ->{ where("line_code IS NOT NULL") }
- scope :not_inline, ->{ where(line_code: nil) }
scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) }
scope :common, ->{ where(noteable_type: ["", nil]) }
@@ -71,52 +53,61 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
+ scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') }
+ scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
+
scope :with_associations, -> do
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
- serialize :st_diff
- before_create :set_diff, if: ->(n) { n.line_code.present? }
+ before_validation :clear_blank_line_code!
class << self
- def discussions_from_notes(notes)
- discussion_ids = []
- discussions = []
-
- notes.each do |note|
- next if discussion_ids.include?(note.discussion_id)
-
- # don't group notes for the main target
- if !note.for_diff_line? && note.for_merge_request?
- discussions << [note]
- else
- discussions << notes.select do |other_note|
- note.discussion_id == other_note.discussion_id
- end
- discussion_ids << note.discussion_id
- end
- end
+ def model_name
+ ActiveModel::Name.new(self, nil, 'note')
+ end
+
+ def build_discussion_id(noteable_type, noteable_id)
+ [:discussion, noteable_type.try(:underscore), noteable_id].join("-")
+ end
- discussions
+ def discussions
+ all.group_by(&:discussion_id).values
end
- def build_discussion_id(type, id, line_code)
- [:discussion, type.try(:underscore), id, line_code].join("-").to_sym
+ def grouped_diff_notes
+ legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end
# Searches for notes matching the given query.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
- # query - The search query as a String.
+ # query - The search query as a String.
+ # as_user - Limit results to those viewable by a specific user
#
# Returns an ActiveRecord::Relation.
- def search(query)
+ def search(query, as_user: nil)
table = arel_table
pattern = "%#{query}%"
- where(table[:note].matches(pattern))
+ found_notes = joins('LEFT JOIN issues ON issues.id = noteable_id').
+ where(table[:note].matches(pattern))
+
+ if as_user
+ found_notes.where('
+ issues.confidential IS NULL
+ OR issues.confidential IS FALSE
+ OR (issues.confidential IS TRUE
+ AND (issues.author_id = :user_id
+ OR issues.assignee_id = :user_id
+ OR issues.project_id IN(:project_ids)))',
+ user_id: as_user.id,
+ project_ids: as_user.authorized_projects.select(:id))
+ else
+ found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE')
+ end
end
def grouped_awards
@@ -137,167 +128,39 @@ class Note < ActiveRecord::Base
system && SystemNoteService.cross_reference?(note)
end
- def max_attachment_size
- current_application_settings.max_attachment_size.megabytes.to_i
- end
-
- def find_diff
- return nil unless noteable
- return @diff if defined?(@diff)
-
- # Don't use ||= because nil is a valid value for @diff
- @diff = noteable.diffs(Commit.max_diff_options).find do |d|
- Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
- end
+ def diff_note?
+ false
end
- def hook_attrs
- attributes
+ def legacy_diff_note?
+ false
end
- def set_diff
- # First lets find notes with same diff
- # before iterating over all mr diffs
- diff = diff_for_line_code unless for_merge_request?
- diff ||= find_diff
-
- self.st_diff = diff.to_hash if diff
- end
-
- def diff
- @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
- end
-
- def diff_for_line_code
- Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff)
- end
-
- # Check if this note is part of an "active" discussion
- #
- # This will always return true for anything except MergeRequest noteables,
- # which have special logic.
- #
- # If the note's current diff cannot be matched in the MergeRequest's current
- # diff, it's considered inactive.
def active?
- return true unless self.diff
- return false unless noteable
- return @active if defined?(@active)
-
- noteable_diff = find_noteable_diff
-
- if noteable_diff
- parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
-
- @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
- else
- @active = false
- end
-
- @active
- end
-
- def diff_file_index
- line_code.split('_')[0] if line_code
- end
-
- def diff_file_name
- diff.new_path if diff
- end
-
- def file_path
- if diff.new_path.present?
- diff.new_path
- elsif diff.old_path.present?
- diff.old_path
- end
- end
-
- def diff_old_line
- line_code.split('_')[1].to_i if line_code
- end
-
- def diff_new_line
- line_code.split('_')[2].to_i if line_code
+ true
end
- def generate_line_code(line)
- Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
- end
-
- def diff_line
- return @diff_line if @diff_line
-
- if diff
- diff_lines.each do |line|
- if generate_line_code(line) == self.line_code
- @diff_line = line.text
- end
- end
- end
-
- @diff_line
- end
-
- def diff_line_type
- return @diff_line_type if @diff_line_type
-
- if diff
- diff_lines.each do |line|
- if generate_line_code(line) == self.line_code
- @diff_line_type = line.type
- end
- end
- end
-
- @diff_line_type
- end
-
- def truncated_diff_lines
- max_number_of_lines = 16
- prev_match_line = nil
- prev_lines = []
-
- highlighted_diff_lines.each do |line|
- if line.type == "match"
- prev_lines.clear
- prev_match_line = line
+ def discussion_id
+ @discussion_id ||=
+ if for_merge_request?
+ [:discussion, :note, id].join("-")
else
- prev_lines << line
-
- break if generate_line_code(line) == self.line_code
-
- prev_lines.shift if prev_lines.length >= max_number_of_lines
+ self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
end
- end
-
- prev_lines
end
- def diff_lines
- @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
- end
-
- def highlighted_diff_lines
- Gitlab::Diff::Highlight.new(diff_lines).highlight
+ def max_attachment_size
+ current_application_settings.max_attachment_size.megabytes.to_i
end
- def discussion_id
- @discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
+ def hook_attrs
+ attributes
end
def for_commit?
noteable_type == "Commit"
end
- def for_commit_diff_line?
- for_commit? && for_diff_line?
- end
-
- def for_diff_line?
- line_code.present?
- end
-
def for_issue?
noteable_type == "Issue"
end
@@ -306,10 +169,6 @@ class Note < ActiveRecord::Base
noteable_type == "MergeRequest"
end
- def for_merge_request_diff_line?
- for_merge_request? && for_diff_line?
- end
-
def for_snippet?
noteable_type == "Snippet"
end
@@ -382,14 +241,8 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank?
end
- # Find the diff on noteable that matches our own
- def find_noteable_diff
- diffs = noteable.diffs(Commit.max_diff_options)
- diffs.find { |d| d.new_path == self.diff.new_path }
- end
-
def awards_supported?
- (for_issue? || for_merge_request?) && !for_diff_line?
+ (for_issue? || for_merge_request?) && !diff_note?
end
def contains_emoji_only?
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 846773752a6..5001738f411 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,16 +1,3 @@
-# == 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/oauth_access_token.rb b/app/models/oauth_access_token.rb
index c78c7f4aa0e..116fb71ac08 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: oauth_access_tokens
-#
-# id :integer not null, primary key
-# resource_owner_id :integer
-# application_id :integer
-# token :string not null
-# refresh_token :string
-# expires_in :integer
-# revoked_at :datetime
-# created_at :datetime not null
-# scopes :string
-#
-
class OauthAccessToken < ActiveRecord::Base
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 1d5f4c50254..82c1c4de3a0 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -1,18 +1,2 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string
-# type :string
-# visibility_level :integer default(0), not null
-#
-
class PersonalSnippet < Snippet
end
diff --git a/app/models/project.rb b/app/models/project.rb
index fdbabc7cf71..74db163bcb5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1,48 +1,3 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# 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
-# 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'
class Project < ActiveRecord::Base
@@ -67,6 +22,7 @@ class Project < ActiveRecord::Base
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
+ default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
# set last_activity_at to the same as created_at
@@ -94,6 +50,8 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
+ alias_attribute :title, :name
+
# Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
@@ -213,17 +171,17 @@ class Project < ActiveRecord::Base
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
- scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
- scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
- scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
- scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
- scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
+ scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
+ scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
+
+ scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
+ scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
state_machine :import_status, initial: :none do
event :import_start do
@@ -246,23 +204,10 @@ class Project < ActiveRecord::Base
state :finished
state :failed
- after_transition any => :started, do: :schedule_add_import_job
- after_transition any => :finished, do: :clear_import_data
+ after_transition any => :finished, do: :reset_cache_and_import_attrs
end
class << self
- def abandoned
- where('projects.last_activity_at < ?', 6.months.ago)
- end
-
- def with_push
- joins(:events).where('events.action = ?', Event::PUSHED)
- end
-
- def active
- joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
- end
-
# Searches for a list of projects based on the query given in `query`.
#
# On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
@@ -324,10 +269,6 @@ class Project < ActiveRecord::Base
projects.iwhere('projects.path' => project_path).take
end
- def find_by_ci_id(id)
- find_by(ci_id: id.to_i)
- end
-
def visibility_levels
Gitlab::VisibilityLevel.options
end
@@ -358,10 +299,6 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC')
end
-
- def visible_to_user(user)
- where(id: user.authorized_projects.select(:id).reorder(nil))
- end
end
def team
@@ -372,6 +309,34 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(path_with_namespace, self)
end
+ def container_registry_path_with_namespace
+ path_with_namespace.downcase
+ end
+
+ def container_registry_repository
+ return unless Gitlab.config.registry.enabled
+
+ @container_registry_repository ||= begin
+ token = Auth::ContainerRegistryAuthenticationService.full_access_token(container_registry_path_with_namespace)
+ url = Gitlab.config.registry.api_url
+ host_port = Gitlab.config.registry.host_port
+ registry = ContainerRegistry::Registry.new(url, token: token, path: host_port)
+ registry.repository(container_registry_path_with_namespace)
+ end
+ end
+
+ def container_registry_repository_url
+ if Gitlab.config.registry.enabled
+ "#{Gitlab.config.registry.host_port}/#{container_registry_path_with_namespace}"
+ end
+ end
+
+ def has_container_registry_tags?
+ return unless container_registry_repository
+
+ container_registry_repository.tags.any?
+ end
+
def commit(id = 'HEAD')
repository.commit(id)
end
@@ -385,10 +350,6 @@ class Project < ActiveRecord::Base
id && persisted?
end
- def schedule_add_import_job
- run_after_commit(:add_import_job)
- end
-
def add_import_job
if forked?
job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
@@ -403,7 +364,7 @@ class Project < ActiveRecord::Base
end
end
- def clear_import_data
+ def reset_cache_and_import_attrs
update(import_error: nil)
ProjectCacheWorker.perform_async(self.id)
@@ -412,14 +373,14 @@ class Project < ActiveRecord::Base
end
def import_url=(value)
- import_url = Gitlab::ImportUrl.new(value)
+ import_url = Gitlab::UrlSanitizer.new(value)
create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
end
def import_url
if import_data && super
- import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
+ import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
import_url.full_url
else
super
@@ -469,17 +430,18 @@ class Project < ActiveRecord::Base
end
def safe_import_url
- result = URI.parse(self.import_url)
- result.password = '*****' unless result.password.nil?
- result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
- result.to_s
- rescue
- self.import_url
+ Gitlab::UrlSanitizer.new(import_url).masked_url
end
def check_limit
unless creator.can_create_project? or namespace.kind == 'group'
- self.errors.add(:limit_reached, "Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
+ projects_limit = creator.projects_limit
+
+ if projects_limit == 0
+ self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
+ else
+ self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
+ end
end
rescue
self.errors.add(:base, "Can't check your ability to create project")
@@ -787,6 +749,11 @@ class Project < ActiveRecord::Base
expire_caches_before_rename(old_path_with_namespace)
+ if has_container_registry_tags?
+ # we currently doesn't support renaming repository if it contains tags in container registry
+ raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
+ end
+
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
@@ -983,13 +950,13 @@ class Project < ActiveRecord::Base
shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
end
- def valid_runners_token? token
+ def valid_runners_token?(token)
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
# TODO (ayufan): For now we use runners_token (backward compatibility)
# In 8.4 every build will have its own individual token valid for time of build
- def valid_build_token? token
+ def valid_build_token?(token)
self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
@@ -1045,6 +1012,24 @@ class Project < ActiveRecord::Base
update_attribute(:pending_delete, true)
end
+ def running_or_pending_build_count(force: false)
+ Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
+ builds.running_or_pending.count(:all)
+ end
+ end
+
+ def mark_import_as_failed(error_message)
+ original_errors = errors.dup
+ sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
+
+ import_fail
+ update_column(:import_error, sanitized_message)
+ rescue ActiveRecord::ActiveRecordError => e
+ Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ ensure
+ @errors = original_errors
+ end
+
def add_export_job(current_user_id:)
job_id = ProjectExportWorker.perform_async(current_user_id, self.id)
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 66f5a609bf5..e52a6bd7c84 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -1,15 +1,3 @@
-# == 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 7830f764ed3..ca8a9b4217b 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: project_import_data
-#
-# 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'
class ProjectImportData < ActiveRecord::Base
@@ -18,7 +6,8 @@ class ProjectImportData < ActiveRecord::Base
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
encode: true,
- mode: :per_attribute_iv_and_salt
+ mode: :per_attribute_iv_and_salt,
+ algorithm: 'aes-256-cbc'
serialize :data, JSON
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 368485a060a..7c23b766763 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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'
class AsanaService < Service
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index ffb7455b014..d839221d315 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index c36ee95e378..1d1780dcfbf 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index f9f4897a065..86a06321e21 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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"
class BuildkiteService < CiService
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 20cdfcaffb2..54da4d74fc5 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :recipients
boolean_accessor :add_pusher
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 28c969fe57f..511b2eac792 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 9bc8f982da6..596c00705ad 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 4d1319eb6f8..6b2b1daa724 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index d8e00e018cc..966dbc41d73 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :drone_url, :token, :enable_ssl_verification
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 2dbd29062df..e0083c43adb 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :send_from_committer_email
prop_accessor :disable_diffs
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 5469049bb5e..d7b6e505191 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
@@ -49,7 +25,7 @@ class ExternalWikiService < Service
def execute(_data)
@response = HTTParty.get(properties['external_wiki_url'], verify: true) rescue nil
- if @response !=200
+ if @response != 200
nil
end
end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 3dc1e0fbe8b..dd00275187f 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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"
class FlowdockService < Service
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index b4c311cf664..598aca5e06d 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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"
class GemnasiumService < Service
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index a92f7226083..bbc312f5215 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
class GitlabCiService < CiService
# We override the active accessor to always make GitLabCiService disabled
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 1adaeeb3b2b..5d17c358330 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include Gitlab::Routing.url_helpers
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index f9ddf588722..0ff4f4c8dd2 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
MAX_COMMITS = 3
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index b9a592d7096..2e5e854fc5e 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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'
class IrkerService < Service
@@ -94,7 +70,7 @@ class IrkerService < Service
private
def get_channels
- return true unless :activated?
+ return true unless activated?
return true if recipients.nil? || recipients.empty?
map_recipients
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 98a3a7c6b86..6ae9b16d3ce 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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, url: true, if: :activated?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index ba68658f0bd..beda89d3963 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
include Gitlab::Routing.url_helpers
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index acaa0c39365..ad19b7795da 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index a640c8cb440..3dd878e4c7d 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
base_uri 'https://api.pushover.net/1'
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index e2137e92c62..11cce3e0561 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 83ffa53a407..cf9e4d5a8b6 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb
index c124cad4afd..69c21b3fc38 100644
--- a/app/models/project_services/slack_service/build_message.rb
+++ b/app/models/project_services/slack_service/build_message.rb
@@ -35,8 +35,8 @@ class SlackService
private
def message
- "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)"
- end
+ "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
+ end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb
index 438ff33fdff..88e053ec192 100644
--- a/app/models/project_services/slack_service/issue_message.rb
+++ b/app/models/project_services/slack_service/issue_message.rb
@@ -34,7 +34,12 @@ class SlackService
private
def message
- "#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*"
+ case state
+ when "opened"
+ "[#{project_link}] Issue #{state} by #{user_name}"
+ else
+ "[#{project_link}] Issue #{issue_link} #{state} by #{user_name}"
+ end
end
def opened_issue?
@@ -42,7 +47,11 @@ class SlackService
end
def description_message
- [{ text: format(description), color: attachment_color }]
+ [{
+ title: issue_title,
+ title_link: issue_url,
+ text: format(description),
+ color: "#C95823" }]
end
def project_link
@@ -50,7 +59,11 @@ class SlackService
end
def issue_link
- "[issue ##{issue_iid}](#{issue_url})"
+ "[#{issue_title}](#{issue_url})"
+ end
+
+ def issue_title
+ "##{issue_iid} #{title}"
end
end
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 4015da31509..b0dcb52eba1 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
include HTTParty
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index b4b2807eba4..25b5d777641 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string
-# type :string
-# visibility_level :integer default(0), not null
-#
-
class ProjectSnippet < Snippet
belongs_to :project
belongs_to :author, class_name: "User"
@@ -23,5 +7,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
- participant :author, :notes
+ participant :author
+ participant :notes_with_associations
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 7c1a61bb0bf..25d82929c0b 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -27,6 +27,10 @@ class ProjectWiki
@project.path_with_namespace + ".wiki"
end
+ def web_url
+ Gitlab::Routing.url_helpers.namespace_project_wiki_url(@project.namespace, @project, :home)
+ end
+
def url_to_repo
gitlab_shell.url_to_repo(path_with_namespace)
end
@@ -40,7 +44,7 @@ class ProjectWiki
end
def wiki_base_path
- ["/", @project.path_with_namespace, "/wikis"].join('')
+ [Gitlab.config.gitlab.relative_url_root, "/", @project.path_with_namespace, "/wikis"].join('')
end
# Returns the Gollum::Wiki object.
@@ -113,7 +117,7 @@ class ProjectWiki
end
def page_title_and_dir(title)
- title_array = title.split("/")
+ title_array = title.split("/")
title = title_array.pop
[title, title_array.join("/")]
end
@@ -142,6 +146,16 @@ class ProjectWiki
wiki
end
+ def hook_attrs
+ {
+ web_url: web_url,
+ git_ssh_url: ssh_url_to_repo,
+ git_http_url: http_url_to_repo,
+ path_with_namespace: path_with_namespace,
+ default_branch: default_branch
+ }
+ end
+
private
def init_repo(path_with_namespace)
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 3d2052c892c..33cf046fa75 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: protected_branches
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string not null
-# created_at :datetime
-# updated_at :datetime
-# developers_can_push :boolean default(FALSE), not null
-#
-
class ProtectedBranch < ActiveRecord::Base
include Gitlab::ShellAdapter
diff --git a/app/models/release.rb b/app/models/release.rb
index dc700d1ea5a..e196b84eb18 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
class Release < ActiveRecord::Base
belongs_to :project
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7aebfe279fb..1ab163510bf 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -81,7 +81,7 @@ class Repository
def commit(id = 'HEAD')
return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id)
- commit = Commit.new(commit, @project) if commit
+ commit = ::Commit.new(commit, @project) if commit
commit
rescue Rugged::OdbError
nil
@@ -195,6 +195,10 @@ class Repository
cache.fetch(:branch_names) { branches.map(&:name) }
end
+ def branch_exists?(branch_name)
+ branch_names.include?(branch_name)
+ end
+
def tag_names
cache.fetch(:tag_names) { raw_repository.tag_names }
end
@@ -241,7 +245,7 @@ class Repository
def cache_keys
%i(size branch_names tag_names commit_count
readme version contribution_guide changelog
- license_blob license_key)
+ license_blob license_key gitignore)
end
def build_cache
@@ -252,6 +256,10 @@ class Repository
end
end
+ def expire_gitignore
+ cache.expire(:gitignore)
+ end
+
def expire_tags_cache
cache.expire(:tag_names)
@tags = nil
@@ -453,7 +461,7 @@ class Repository
def version
cache.fetch(:version) do
tree(:head).blobs.find do |file|
- file.name.downcase == 'version'
+ file.name.casecmp('version').zero?
end
end
end
@@ -468,33 +476,37 @@ class Repository
def changelog
cache.fetch(:changelog) do
- tree(:head).blobs.find do |file|
- file.name =~ /\A(changelog|history|changes|news)/i
- end
+ file_on_head(/\A(changelog|history|changes|news)/i)
end
end
def license_blob
- return nil if !exists? || empty?
+ return nil unless head_exists?
cache.fetch(:license_blob) do
- tree(:head).blobs.find do |file|
- file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
- end
+ file_on_head(/\A(licen[sc]e|copying)(\..+|\z)/i)
end
end
def license_key
- return nil if !exists? || empty?
+ return nil unless head_exists?
cache.fetch(:license_key) do
Licensee.license(path).try(:key)
end
end
- def gitlab_ci_yml
+ def gitignore
return nil if !exists? || empty?
+ cache.fetch(:gitignore) do
+ file_on_head(/\A\.gitignore\z/)
+ end
+ end
+
+ def gitlab_ci_yml
+ return nil unless head_exists?
+
@gitlab_ci_yml ||= tree(:head).blobs.find do |file|
file.name == '.gitlab-ci.yml'
end
@@ -795,7 +807,7 @@ class Repository
def check_revert_content(commit, base_branch)
source_sha = find_branch(base_branch).target
args = [commit.id, source_sha]
- args << { mainline: 1 } if commit.merge_commit?
+ args << { mainline: 1 } if commit.merge_commit?
revert_index = rugged.revert_commit(*args)
return false if revert_index.conflicts?
@@ -809,7 +821,7 @@ class Repository
def check_cherry_pick_content(commit, base_branch)
source_sha = find_branch(base_branch).target
args = [commit.id, source_sha]
- args << 1 if commit.merge_commit?
+ args << 1 if commit.merge_commit?
cherry_pick_index = rugged.cherrypick_commit(*args)
return false if cherry_pick_index.conflicts?
@@ -850,7 +862,7 @@ class Repository
def search_files(query, ref)
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
+ args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
@@ -960,12 +972,6 @@ class Repository
end
end
- def main_language
- return if empty? || rugged.head_unborn?
-
- Linguist::Repository.new(rugged, rugged.head.target_id).language
- end
-
def avatar
return nil unless exists?
@@ -981,4 +987,12 @@ class Repository
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
+
+ def head_exists?
+ exists? && !empty? && !rugged.head_unborn?
+ end
+
+ def file_on_head(regex)
+ tree(:head).blobs.find { |file| file.name =~ regex }
+ end
end
diff --git a/app/models/security_event.rb b/app/models/security_event.rb
index 0bee03974f1..d131c11cb6c 100644
--- a/app/models/security_event.rb
+++ b/app/models/security_event.rb
@@ -1,16 +1,2 @@
-# == Schema Information
-#
-# Table name: audit_events
-#
-# id :integer not null, primary key
-# author_id :integer not null
-# type :string not null
-# entity_id :integer not null
-# entity_type :string not null
-# details :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class SecurityEvent < AuditEvent
end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 99279a2e083..375f195dba7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: sent_notifications
-#
-# id :integer not null, primary key
-# project_id :integer
-# noteable_id :integer
-# noteable_type :string
-# recipient_id :integer
-# commit_id :string
-# reply_key :string not null
-# line_code :string
-#
-
class SentNotification < ActiveRecord::Base
belongs_to :project
belongs_to :noteable, polymorphic: true
diff --git a/app/models/service.rb b/app/models/service.rb
index bf16a545307..de3fd24584a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -1,27 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string
-# title :string
-# project_id :integer
-# 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)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# 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
# and implement a set of methods
class Service < ActiveRecord::Base
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 2f905a90942..407697b745c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string
-# type :string
-# visibility_level :integer default(0), not null
-#
-
class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Linguist::BlobHelper
@@ -46,7 +30,8 @@ class Snippet < ActiveRecord::Base
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
- participant :author, :notes
+ participant :author
+ participant :notes_with_associations
def self.reference_prefix
'$'
@@ -116,6 +101,10 @@ class Snippet < ActiveRecord::Base
content.lines.count > 1000
end
+ def notes_with_associations
+ notes.includes(:author, :project)
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index f49eb7d88e2..12df68ef83b 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -1,20 +1,3 @@
-# == 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 242faa7d32e..3b8aa1eb866 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: subscriptions
-#
-# id :integer not null, primary key
-# user_id :integer
-# subscribable_id :integer
-# subscribable_type :string
-# subscribed :boolean
-# created_at :datetime
-# updated_at :datetime
-#
-
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :subscribable, polymorphic: true
diff --git a/app/models/todo.rb b/app/models/todo.rb
index d85f7bfdf57..3a091373329 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,24 +1,7 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
class Todo < ActiveRecord::Base
- ASSIGNED = 1
- MENTIONED = 2
+ ASSIGNED = 1
+ MENTIONED = 2
+ BUILD_FAILED = 3
belongs_to :author, class_name: "User"
belongs_to :note
@@ -46,6 +29,10 @@ class Todo < ActiveRecord::Base
state :done
end
+ def build_failed?
+ action == BUILD_FAILED
+ end
+
def body
if note.present?
note.note
diff --git a/app/models/user.rb b/app/models/user.rb
index 959b1f93758..172845c9d25 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,68 +1,3 @@
-# == Schema Information
-#
-# Table name: users
-#
-# id :integer not null, primary key
-# 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
-# last_sign_in_ip :string
-# created_at :datetime
-# updated_at :datetime
-# name :string
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# 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
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# username :string
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# 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
-# confirmation_token :string
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string default(""), not null
-# notification_email :string
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# 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 default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
-# unlock_token :string
-# otp_grace_period_started_at :datetime
-# ldap_email :boolean default(FALSE), not null
-# external :boolean default(FALSE)
-#
-
require 'carrierwave/orm/activerecord'
class User < ActiveRecord::Base
@@ -85,14 +20,19 @@ class User < ActiveRecord::Base
default_value_for :hide_no_password, false
default_value_for :theme_id, gitlab_config.default_theme
+ attr_encrypted :otp_secret,
+ key: Gitlab::Application.config.secret_key_base,
+ mode: :per_attribute_iv_and_salt,
+ algorithm: 'aes-256-cbc'
+
devise :two_factor_authenticatable,
- otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
+ otp_secret_encryption_key: Gitlab::Application.config.secret_key_base
alias_attribute :two_factor_enabled, :otp_required_for_login
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
- devise :lockable, :async, :recoverable, :rememberable, :trackable,
+ devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
attr_accessor :force_random_password
@@ -177,6 +117,7 @@ class User < ActiveRecord::Base
before_save :ensure_external_user_rights
after_save :ensure_namespace_correct
after_initialize :set_projects_limit
+ before_create :check_confirmation_email
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -372,6 +313,10 @@ class User < ActiveRecord::Base
@reset_token
end
+ def check_confirmation_email
+ skip_confirmation! unless current_application_settings.send_user_confirmation_email
+ end
+
def recently_sent_password_reset?
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
@@ -446,17 +391,17 @@ class User < ActiveRecord::Base
Project.where("projects.id IN (#{projects_union.to_sql})")
end
+ def viewable_starred_projects
+ starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (#{projects_union.to_sql})",
+ [Project::PUBLIC, Project::INTERNAL])
+ end
+
def owned_projects
@owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?',
owned_groups.select(:id), namespace.id).joins(:namespace)
end
- # Team membership in authorized projects
- def tm_in_authorized_projects
- ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
- end
-
def is_admin?
admin
end
@@ -546,10 +491,6 @@ class User < ActiveRecord::Base
"#{name} (#{username})"
end
- def tm_of(project)
- project.project_member_by_id(self.id)
- end
-
def already_forked?(project)
!!fork_of(project)
end
@@ -835,6 +776,23 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source)
end
+ def assigned_open_merge_request_count(force: false)
+ Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
+ assigned_merge_requests.opened.count
+ end
+ end
+
+ def assigned_open_issues_count(force: false)
+ Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do
+ assigned_issues.opened.count
+ end
+ end
+
+ def update_cache_counts
+ assigned_open_merge_request_count(force: true)
+ assigned_open_issues_count(force: true)
+ end
+
private
def projects_union
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 413f3f485a8..0dfe597317e 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: users_star_projects
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# user_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
new file mode 100644
index 00000000000..e57b95f21ec
--- /dev/null
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -0,0 +1,87 @@
+module Auth
+ class ContainerRegistryAuthenticationService < BaseService
+ include Gitlab::CurrentSettings
+
+ AUDIENCE = 'container_registry'
+
+ def execute
+ return error('not found', 404) unless registry.enabled
+
+ unless current_user || project
+ return error('forbidden', 403) unless scope
+ end
+
+ { token: authorized_token(scope).encoded }
+ end
+
+ def self.full_access_token(*names)
+ registry = Gitlab.config.registry
+ token = JSONWebToken::RSAToken.new(registry.key)
+ token.issuer = registry.issuer
+ token.audience = AUDIENCE
+ token.expire_time = token_expire_at
+ token[:access] = names.map do |name|
+ { type: 'repository', name: name, actions: %w(*) }
+ end
+ token.encoded
+ end
+
+ private
+
+ def authorized_token(*accesses)
+ token = JSONWebToken::RSAToken.new(registry.key)
+ token.issuer = registry.issuer
+ token.audience = params[:service]
+ token.subject = current_user.try(:username)
+ token.expire_time = ContainerRegistryAuthenticationService.token_expire_at
+ token[:access] = accesses.compact
+ token
+ end
+
+ def scope
+ return unless params[:scope]
+
+ @scope ||= process_scope(params[:scope])
+ end
+
+ def process_scope(scope)
+ type, name, actions = scope.split(':', 3)
+ actions = actions.split(',')
+ return unless type == 'repository'
+
+ process_repository_access(type, name, actions)
+ end
+
+ def process_repository_access(type, name, actions)
+ requested_project = Project.find_with_namespace(name)
+ return unless requested_project
+
+ actions = actions.select do |action|
+ can_access?(requested_project, action)
+ end
+
+ { type: type, name: name, actions: actions } if actions.present?
+ end
+
+ def can_access?(requested_project, requested_action)
+ return false unless requested_project.container_registry_enabled?
+
+ case requested_action
+ when 'pull'
+ requested_project == project || can?(current_user, :read_container_image, requested_project)
+ when 'push'
+ requested_project == project || can?(current_user, :create_container_image, requested_project)
+ else
+ false
+ end
+ end
+
+ def registry
+ Gitlab.config.registry
+ end
+
+ def self.token_expire_at
+ Time.now + current_application_settings.container_registry_token_expire_delay.minutes
+ end
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
new file mode 100644
index 00000000000..5bc0c31cb42
--- /dev/null
+++ b/app/services/ci/create_pipeline_service.rb
@@ -0,0 +1,50 @@
+module Ci
+ class CreatePipelineService < BaseService
+ def execute
+ pipeline = project.ci_commits.new(params)
+
+ unless ref_names.include?(params[:ref])
+ pipeline.errors.add(:base, 'Reference not found')
+ return pipeline
+ end
+
+ unless commit
+ pipeline.errors.add(:base, 'Commit not found')
+ return pipeline
+ end
+
+ unless can?(current_user, :create_pipeline, project)
+ pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline')
+ return pipeline
+ end
+
+ begin
+ Ci::Commit.transaction do
+ pipeline.sha = commit.id
+
+ unless pipeline.config_processor
+ pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
+ raise ActiveRecord::Rollback
+ end
+
+ pipeline.save!
+ pipeline.create_builds(current_user)
+ end
+ rescue
+ pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.')
+ end
+
+ pipeline
+ end
+
+ private
+
+ def ref_names
+ @ref_names ||= project.repository.ref_names
+ end
+
+ def commit
+ @commit ||= project.commit(params[:ref])
+ end
+ end
+end
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 0d2aa1ff03d..5b6fefe669e 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -18,19 +18,16 @@ class CreateCommitBuildsService
return false
end
- commit = project.ci_commit(sha, ref)
- unless commit
- commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
+ commit = Ci::Commit.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
- # Skip creating ci_commit when no gitlab-ci.yml is found
- unless commit.ci_yaml_file
- return false
- end
-
- # Create a new ci_commit
- commit.save!
+ # Skip creating ci_commit when no gitlab-ci.yml is found
+ unless commit.ci_yaml_file
+ return false
end
+ # Create a new ci_commit
+ commit.save!
+
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 66136b62617..a886f35981f 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -53,10 +53,6 @@ class GitPushService < BaseService
# could cause the last commit of a merge request to change.
update_merge_requests
- # Checks if the main language has changed in the project and if so
- # it updates it accordingly
- update_main_language
-
perform_housekeeping
end
@@ -64,19 +60,6 @@ class GitPushService < BaseService
@project.repository.copy_gitattributes(params[:ref])
end
- def update_main_language
- # Performance can be bad so for now only check main_language once
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
- return if @project.main_language.present?
-
- return unless is_default_branch?
- return unless push_to_new_branch? || push_to_existing_branch?
-
- current_language = @project.repository.main_language
- @project.update_attributes(main_language: current_language)
- true
- end
-
protected
def update_merge_requests
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 7410442609d..299a0a967b0 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -23,7 +23,7 @@ class GitTagPushService < BaseService
commits = []
message = nil
- if !Gitlab::Git.blank_ref?(params[:newrev])
+ unless Gitlab::Git.blank_ref?(params[:newrev])
tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
if tag && tag.target == params[:newrev]
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 3563cbaa997..c7d406cc331 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -24,6 +24,10 @@ module Issues
todo_service.reassigned_issue(issue, current_user)
end
+ if issue.previous_changes.include?('confidential')
+ create_confidentiality_note(issue)
+ end
+
added_labels = issue.labels - old_labels
if added_labels.present?
notification_service.relabeled_issue(issue, added_labels, current_user)
@@ -37,5 +41,11 @@ module Issues
def close_service
Issues::CloseService
end
+
+ private
+
+ def create_confidentiality_note(issue)
+ SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
+ end
end
end
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
new file mode 100644
index 00000000000..566049525cb
--- /dev/null
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -0,0 +1,17 @@
+module MergeRequests
+ class AddTodoWhenBuildFailsService < MergeRequests::BaseService
+ # Adds a todo to the parent merge_request when a CI build fails
+ def execute(commit_status)
+ each_merge_request(commit_status) do |merge_request|
+ todo_service.merge_request_build_failed(merge_request)
+ end
+ end
+
+ # Closes any pending build failed todos for the parent MRs when a build is retried
+ def close(commit_status)
+ each_merge_request(commit_status) do |merge_request|
+ todo_service.merge_request_build_retried(merge_request)
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index e6837a18696..9d7fca6882d 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -38,5 +38,30 @@ module MergeRequests
def filter_params
super(:merge_request)
end
+
+ def merge_request_from(commit_status)
+ branches = commit_status.ref
+
+ # This is for ref-less builds
+ branches ||= @project.repository.branch_names_contains(commit_status.sha)
+
+ return [] if branches.blank?
+
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
+ merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
+
+ merge_requests.uniq.select(&:source_project)
+ end
+
+ def each_merge_request(commit_status)
+ merge_request_from(commit_status).each do |merge_request|
+ ci_commit = merge_request.ci_commit
+
+ next unless ci_commit
+ next unless ci_commit.sha == commit_status.sha
+
+ yield merge_request, ci_commit
+ end
+ end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index cd4230aa5e4..1b48899bb0a 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -66,7 +66,7 @@ module MergeRequests
commits = merge_request.compare_commits
if commits && commits.count == 1
commit = commits.first
- merge_request.title = commit.title
+ 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
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 33609d01f20..96a25330af1 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -8,11 +8,14 @@ module MergeRequests
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
filter_params
- label_params = params[:label_ids]
- merge_request = MergeRequest.new(params.except(:label_ids))
+ label_params = params.delete(:label_ids)
+ force_remove_source_branch = params.delete(:force_remove_source_branch)
+
+ merge_request = MergeRequest.new(params)
merge_request.source_project = source_project
merge_request.target_project ||= source_project
merge_request.author = current_user
+ merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch
if merge_request.save
merge_request.update_attributes(label_ids: label_params)
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 9a58383b398..9aaf5a5e561 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -45,10 +45,14 @@ module MergeRequests
def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
- if params[:should_remove_source_branch].present?
- DeleteBranchService.new(@merge_request.source_project, current_user).
+ if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
+ DeleteBranchService.new(@merge_request.source_project, branch_deletion_user).
execute(merge_request.source_branch)
end
end
+
+ def branch_deletion_user
+ @merge_request.force_remove_source_branch? ? @merge_request.author : current_user
+ end
end
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
index d6af12f9739..8fd6a4ea1f6 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -20,15 +20,9 @@ module MergeRequests
# Triggers the automatic merge of merge_request once the build succeeds
def trigger(commit_status)
- merge_requests = merge_request_from(commit_status)
-
- merge_requests.each do |merge_request|
+ each_merge_request(commit_status) do |merge_request, ci_commit|
next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable?
-
- ci_commit = merge_request.ci_commit
- next unless ci_commit
- next unless ci_commit.sha == commit_status.sha
next unless ci_commit.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
@@ -47,20 +41,5 @@ module MergeRequests
end
end
- private
-
- def merge_request_from(commit_status)
- branches = commit_status.ref
-
- # This is for ref-less builds
- branches ||= @project.repository.branch_names_contains(commit_status.sha)
-
- return [] if branches.blank?
-
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
- merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
-
- merge_requests.uniq.select(&:source_project)
- end
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 8b3d56c2b4c..fe0579744b4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -12,6 +12,7 @@ module MergeRequests
close_merge_requests
reload_merge_requests
reset_merge_when_build_succeeds
+ mark_pending_todos_done
# Leave a system note if a branch was deleted/added
if branch_added? || branch_removed?
@@ -80,6 +81,12 @@ module MergeRequests
merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
end
+ def mark_pending_todos_done
+ merge_requests_for_source_branch.each do |merge_request|
+ todo_service.merge_request_push(merge_request, @current_user)
+ end
+ end
+
def find_new_commits
if branch_added?
@commits = []
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 477c64e7377..026a37997d4 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,6 +11,8 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
+ merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
+
update(merge_request)
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 01586994813..2bb312bb252 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,8 +5,6 @@ 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)
@@ -15,14 +13,5 @@ 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/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index ba50305dbd5..eb73948006e 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -4,6 +4,10 @@ module Projects
@project.issues.visible_to_user(current_user).opened.select([:iid, :title])
end
+ def milestones
+ @project.milestones.active.reorder(due_date: :asc, title: :asc).select([:iid, :title])
+ end
+
def merge_requests
@project.merge_requests.opened.select([:iid, :title])
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 501e58c1407..61cac5419ad 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -6,6 +6,7 @@ module Projects
def execute
forked_from_project_id = params.delete(:forked_from_project_id)
+ import_data = params.delete(:import_data)
@project = Project.new(params)
@@ -49,22 +50,20 @@ module Projects
@project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
end
- Project.transaction do
- @project.save
+ save_project_and_import_data(import_data)
- if @project.persisted? && !@project.import?
- raise 'Failed to create repository' unless @project.create_repository
- end
- end
+ @project.import_start if @project.import?
after_create_actions if @project.persisted?
+ if @project.errors.empty?
+ @project.add_import_job if @project.import?
+ else
+ fail(error: @project.errors.full_messages.join(', '))
+ end
@project
rescue => e
- message = "Unable to save project: #{e.message}"
- Rails.logger.error(message)
- @project.errors.add(:base, message) if @project
- @project
+ fail(error: e.message)
end
protected
@@ -93,8 +92,30 @@ module Projects
unless @project.group
@project.team << [current_user, :master, current_user]
end
+ end
- @project.import_start if @project.import?
+ def save_project_and_import_data(import_data)
+ Project.transaction do
+ @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
+
+ if @project.save && !@project.import?
+ raise 'Failed to create repository' unless @project.create_repository
+ end
+ end
+ end
+
+ def fail(error:)
+ message = "Unable to save project. Error: #{error}"
+ message << "Project ID: #{@project.id}" if @project && @project.id
+
+ Rails.logger.error(message)
+
+ if @project && @project.import?
+ @project.errors.add(:base, message)
+ @project.mark_import_as_failed(message)
+ end
+
+ @project
end
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 19aab999e00..f09072975c3 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -26,6 +26,10 @@ module Projects
Project.transaction do
project.destroy!
+ unless remove_registry_tags
+ raise_error('Failed to remove project container registry. Please try again or contact administrator')
+ end
+
unless remove_repository(repo_path)
raise_error('Failed to remove project repository. Please try again or contact administrator')
end
@@ -35,7 +39,7 @@ module Projects
end
end
- log_info("Project \"#{project.name}\" was removed")
+ log_info("Project \"#{project.path_with_namespace}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy)
true
end
@@ -59,6 +63,12 @@ module Projects
end
end
+ def remove_registry_tags
+ return true unless Gitlab.config.registry.enabled
+
+ project.container_registry_repository.delete_tags
+ end
+
def raise_error(message)
raise DestroyError.new(message)
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 0577ae778d5..de6dc38cc8e 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -3,7 +3,7 @@ module Projects
def execute
new_params = {
forked_from_project_id: @project.id,
- visibility_level: @project.visibility_level,
+ visibility_level: allowed_visibility_level,
description: @project.description,
name: @project.name,
path: @project.path,
@@ -19,5 +19,17 @@ module Projects
new_project = CreateService.new(current_user, new_params).execute
new_project
end
+
+ private
+
+ def allowed_visibility_level
+ project_level = @project.visibility_level
+
+ if Gitlab::VisibilityLevel.non_restricted_level?(project_level)
+ project_level
+ else
+ Gitlab::VisibilityLevel.highest_allowed_level
+ end
+ end
end
end
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index 3b7c36f0908..43db29315a1 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -22,7 +22,7 @@ module Projects
end
def execute
- raise LeaseTaken if !try_obtain_lease
+ raise LeaseTaken unless try_obtain_lease
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index ef15ef6a473..c4838d31f2f 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -39,7 +39,7 @@ module Projects
begin
gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
rescue Gitlab::Shell::Error => e
- raise Error, e.message
+ raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 111b3ec05ea..03b57dea51e 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,6 +34,11 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
+ if project.has_container_registry_tags?
+ # we currently doesn't support renaming repository if it contains tags in container registry
+ raise TransferError.new('Project cannot be transferred, because tags are present in its container registry')
+ end
+
project.expire_caches_before_rename(old_path)
# Apply new namespace id and visibility level
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index e43b5b51e5b..1fb72cf89e9 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -85,7 +85,7 @@ class SystemHooksService
path_with_namespace: model.path_with_namespace,
project_id: model.id,
owner_name: owner.name,
- owner_email: owner.respond_to?(:email) ? owner.email : "",
+ owner_email: owner.respond_to?(:email) ? owner.email : "",
project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase
}
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 4bdb1b0c074..4e8fa0818b9 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -169,12 +169,33 @@ class SystemNoteService
#
# Returns the created Note object
def self.change_title(noteable, project, author, old_title)
- return unless noteable.respond_to?(:title)
+ new_title = noteable.title.dup
- body = "Title changed from **#{old_title}** to **#{noteable.title}**"
+ old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs
+
+ marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true)
+ marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true)
+
+ body = "Changed title: **#{marked_old_title}** → **#{marked_new_title}**"
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when the confidentiality changes
+ #
+ # issue - Issue object
+ # project - Project owning the issue
+ # author - User performing the change
+ #
+ # Example Note text:
+ #
+ # "Made the issue confidential"
+ #
+ # Returns the created Note object
+ def self.change_issue_confidentiality(issue, project, author)
+ body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
+ create_note(noteable: issue, project: project, author: author, note: body)
+ end
+
# Called when a branch in Noteable is changed
#
# noteable - Noteable object
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 42c5bca90fd..4bf4e144727 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -80,6 +80,30 @@ class TodoService
mark_pending_todos_as_done(merge_request, current_user)
end
+ # When a build fails on the HEAD of a merge request we should:
+ #
+ # * create a todo for that user to fix it
+ #
+ def merge_request_build_failed(merge_request)
+ create_build_failed_todo(merge_request)
+ end
+
+ # When a new commit is pushed to a merge request we should:
+ #
+ # * mark all pending todos related to the merge request for that user as done
+ #
+ def merge_request_push(merge_request, current_user)
+ mark_pending_todos_as_done(merge_request, current_user)
+ end
+
+ # When a build is retried to a merge request we should:
+ #
+ # * mark all pending todos related to the merge request for the author as done
+ #
+ def merge_request_build_retried(merge_request)
+ mark_pending_todos_as_done(merge_request, merge_request.author)
+ end
+
# When create a note we should:
#
# * mark all pending todos related to the noteable for the note author as done
@@ -145,6 +169,12 @@ class TodoService
create_todos(mentioned_users, attributes)
end
+ def create_build_failed_todo(merge_request)
+ author = merge_request.author
+ attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED)
+ create_todos(author, attributes)
+ end
+
def attributes_for_target(target)
attributes = {
project_id: target.project.id,
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
index 9162f128602..4c0a2c6b4d8 100644
--- a/app/services/wiki_pages/base_service.rb
+++ b/app/services/wiki_pages/base_service.rb
@@ -6,9 +6,8 @@ module WikiPages
object_kind: page.class.name.underscore,
user: current_user.hook_attrs,
project: @project.hook_attrs,
- object_attributes: page.hook_attrs,
- # DEPRECATED
- repository: @project.hook_attrs.slice(:name, :url, :description, :homepage)
+ wiki: @project.wiki.hook_attrs,
+ object_attributes: page.hook_attrs
}
page_url = Gitlab::UrlBuilder.build(page)
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index 2ab01704b77..862b86d9d4a 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -16,7 +16,7 @@
.light.small
= time_ago_with_tooltip(abuse_report.created_at)
%td
- = markdown(abuse_report.message.squish!, pipeline: :single_line)
+ = markdown(abuse_report.message.squish!, pipeline: :single_line, author: reporter)
%td
- if user
= link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index e0d8d16a954..f149f9eb431 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -106,9 +106,22 @@
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
+ = f.label :send_user_confirmation_email do
+ = f.check_box :send_user_confirmation_email
+ Send confirmation email on sign-up
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
= f.label :signin_enabled do
= f.check_box :signin_enabled
Sign-in enabled
+ - if omniauth_enabled? && button_based_providers.any?
+ .form-group
+ = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2'
+ .col-sm-10
+ .btn-group{ data: { toggle: 'buttons' } }
+ - oauth_providers_checkboxes.each do |source|
+ = source
.form-group
= f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
@@ -165,6 +178,14 @@
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
+ - if Gitlab.config.registry.enabled
+ %fieldset
+ %legend Container Registry
+ .form-group
+ = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :container_registry_token_expire_delay, class: 'form-control'
+
%fieldset
%legend Metrics
%p
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 804d7851bdb..d74cf8598e8 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -20,7 +20,7 @@
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.row-content-block.second-block
- #{(@scope || 'running').capitalize} builds
+ #{(@scope || 'all').capitalize} builds
%ul.content-list
- if @builds.blank?
@@ -47,4 +47,3 @@
= render "admin/builds/build", build: build
= paginate @builds, theme: 'gitlab'
-
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
new file mode 100644
index 00000000000..c2313986a7f
--- /dev/null
+++ b/app/views/admin/health_check/show.html.haml
@@ -0,0 +1,49 @@
+- page_title "Health Check"
+
+%h3.page-title
+ Health Check
+.bs-callout.clearfix
+ .pull-left
+ %p
+ Access token is
+ %code#health-check-token= current_application_settings.health_check_access_token
+ = button_to reset_health_check_token_admin_application_settings_path,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: 'Are you sure you want to reset the health check token?' } do
+ = icon('refresh')
+ Reset health check access token
+%p.light
+ Health information can be retrieved as plain text, JSON, or XML using:
+ %ul
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token)
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
+
+%p.light
+ You can also ask for the status of specific services:
+ %ul
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
+ %li
+ %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
+
+%hr
+.panel.panel-default
+ .panel-heading
+ Current Status:
+ - if @errors.blank?
+ = icon('circle', class: 'cgreen')
+ Healthy
+ - else
+ = icon('warning', class: 'cred')
+ Unhealthy
+ .panel-body
+ - if @errors.blank?
+ No Health Problems Detected
+ - else
+ = @errors
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 6745e58deca..36b21eefdee 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -11,18 +11,10 @@
= link_to admin_runner_path(runner) do
= runner.short_sha
%td
- .runner-description
- = runner.description
- %span (#{link_to 'edit', '#', class: 'edit-runner-link'})
- .runner-description-form.hide
- = form_for [:admin, runner], remote: true, html: { class: 'form-inline' } do |f|
- .form-group
- = f.text_field :description, class: 'form-control'
- = f.submit 'Save', class: 'btn'
- %span (#{link_to 'cancel', '#', class: 'cancel'})
+ = runner.description
%td
- if runner.shared?
- \-
+ n/a
- else
= runner.projects.count(:all)
%td
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 8700b4820cd..c3784bf7192 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -9,8 +9,6 @@
%span.runner-state.runner-state-specific
Specific
-
-
- if @runner.shared?
.bs-callout.bs-callout-success
%h4 This runner will process builds from ALL UNASSIGNED projects
@@ -22,25 +20,9 @@
%h4 This runner will process builds only from ASSIGNED projects
%p You can't make this a shared runner.
%hr
-= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
- .form-group
- = label_tag :token, class: 'control-label' do
- Token
- .col-sm-10
- = f.text_field :token, class: 'form-control', readonly: true
- .form-group
- = label_tag :description, class: 'control-label' do
- Description
- .col-sm-10
- = f.text_field :description, class: 'form-control'
- .form-group
- = label_tag :tag_list, class: 'control-label' do
- Tags
- .col-sm-10
- = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
- .help-block You can setup builds to only use runners with specific tags
- .form-actions
- = f.submit 'Save', class: 'btn btn-save'
+
+.append-bottom-20
+ = render '/projects/runners/form', runner: @runner, runner_form_url: admin_runner_path(@runner)
.row
.col-md-6
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 0d7b1b30dc3..83c0c6da21b 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -6,8 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.any?
- @issues.each do |issue|
- issue_to_atom(xml, issue)
- end
+ xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
end
-
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index d4daf07c6c0..fb5be63b472 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -6,7 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id dashboard_projects_url
xml.updated @events[0].updated_at.xmlschema if @events[0]
- @events.each do |event|
- event_to_atom(xml, event)
- end
+ xml << render(partial: 'events/event', collection: @events) if @events.any?
end
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index aa0aff86d4d..98f302d2f93 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,13 +1,15 @@
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} }
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
-
.todo-title.title
- %span.author-name
- - if todo.author
- = link_to_author(todo)
- - else
- (removed)
+ - unless todo.build_failed?
+ = todo_target_state_pill(todo)
+
+ %span.author-name
+ - if todo.author
+ = link_to_author(todo)
+ - else
+ (removed)
%span.todo-label
= todo_action_name(todo)
- if todo.target
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
deleted file mode 100644
index c6fa8f0ee36..00000000000
--- a/app/views/devise/mailer/confirmation_instructions.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-<p>Welcome <%= @resource.name %>!</p>
-
-<% if @resource.unconfirmed_email.present? %>
- <p>You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:</p>
-<% else %>
- <p>You can confirm your account through the link below:</p>
-<% end %>
-
-<p><%= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) %></p>
diff --git a/app/views/devise/mailer/confirmation_instructions.html.haml b/app/views/devise/mailer/confirmation_instructions.html.haml
new file mode 100644
index 00000000000..086bb8e083d
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.haml
@@ -0,0 +1,16 @@
+.center
+ - if @resource.unconfirmed_email.present?
+ #content
+ %h2= @resource.unconfirmed_email
+ %p Click the link below to confirm your email address.
+ #cta
+ = link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
+ - else
+ #content
+ - if Gitlab.com?
+ %h2 Thanks for signing up to GitLab!
+ - else
+ %h2 Welcome, #{@resource.name}!
+ %p To get started, click the link below to confirm your account.
+ #cta
+ = link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token)
diff --git a/app/views/devise/mailer/confirmation_instructions.text.erb b/app/views/devise/mailer/confirmation_instructions.text.erb
new file mode 100644
index 00000000000..9f76edb76a4
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.text.erb
@@ -0,0 +1,9 @@
+Welcome, <%= @resource.name %>!
+
+<% if @resource.unconfirmed_email.present? %>
+You can confirm your email (<%= @resource.unconfirmed_email %>) through the link below:
+<% else %>
+You can confirm your account through the link below:
+<% end %>
+
+<%= confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index d65fa60025c..28194506acc 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -4,7 +4,7 @@
= render 'devise/shared/signin_box'
-# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- - if omniauth_enabled? && devise_mapping.omniauthable?
+ - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
.clearfix.prepend-top-20
= render 'devise/shared/omniauth_box'
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index c9d1e454a5e..8c6a1552a53 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -4,6 +4,7 @@
%h3 Two-factor Authentication
.login-body
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
+ = f.hidden_field :remember_me, value: params[resource_name][:remember_me]
= 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
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index ecf680e7b23..de18bc2d844 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -1,7 +1,7 @@
%p
%span.light
Sign in with &nbsp;
- - providers = button_based_providers
+ - providers = enabled_button_based_providers
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index eae80e5210f..ce050007204 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -1,4 +1,4 @@
-%h3.page-title Authorize required
+%h3.page-title Authorization required
%main{:role => "main"}
%p.h4
Authorize
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index dce4081288c..1bc9f604438 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: '', title: truncate_sha(commit[:id])
&middot;
- = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
+ = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder
new file mode 100644
index 00000000000..7890e717aa7
--- /dev/null
+++ b/app/views/events/_event.atom.builder
@@ -0,0 +1,20 @@
+return unless event.visible_to_user?(current_user)
+
+xml.entry do
+ xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
+ xml.link href: event_feed_url(event)
+ xml.title truncate(event_feed_title(event), length: 80)
+ xml.updated event.created_at.xmlschema
+ xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email))
+
+ xml.author do
+ xml.name event.author_name
+ xml.email event.author_email
+ end
+
+ xml.summary(type: "xhtml") do |summary|
+ event_summary = event_feed_summary(event)
+
+ summary << event_summary unless event_summary.nil?
+ end
+end
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml
index fad65310021..083c3936212 100644
--- a/app/views/events/_event_issue.atom.haml
+++ b/app/views/events/_event_issue.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(issue.description, pipeline: :atom, project: issue.project)
+ = markdown(issue.description, pipeline: :atom, project: issue.project, author: issue.author)
diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml
index 19bdc7b9ca5..d7e05600627 100644
--- a/app/views/events/_event_merge_request.atom.haml
+++ b/app/views/events/_event_merge_request.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
+ = markdown(merge_request.description, pipeline: :atom, project: merge_request.project, author: merge_request.author)
diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml
index b730ebbd5f9..1154f982821 100644
--- a/app/views/events/_event_note.atom.haml
+++ b/app/views/events/_event_note.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(note.note, pipeline: :atom, project: note.project)
+ = markdown(note.note, pipeline: :atom, project: note.project, author: note.author)
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index b271b9daff1..28bee1d0a33 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
+ %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project, author: event.author)
- if event.commits_count > 15
%p
%i
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index c994e3b997d..c7f29f2fc0e 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -4,7 +4,7 @@
= event_action_name(event)
- if event.target
- %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target]
+ %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
= event_preposition(event)
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 235bd46107e..dc4ff17e31a 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -15,7 +15,7 @@
%ul.well-list.event_commits
- few_commits = event.commits[0...2]
- few_commits.each do |commit|
- = render "events/commit", commit: commit, project: project
+ = render "events/commit", commit: commit, project: project, event: event
- create_mr = event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project)
- if event.commits_count > 1
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
index dc76599b776..71cc4d87b1f 100644
--- a/app/views/groups/_activities.html.haml
+++ b/app/views/groups/_activities.html.haml
@@ -4,7 +4,7 @@
.nav-block
- if current_user
.controls
- = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
+ = link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index 486d1d8587a..c19671295af 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -1,13 +1,10 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
- xml.title "#{@user.name} issues"
- xml.link href: issues_dashboard_url(format: :atom, private_token: @user.private_token), rel: "self", type: "application/atom+xml"
- xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
- xml.id issues_dashboard_url
+ xml.title "#{@group.name} issues"
+ xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
+ xml.link href: issues_group_url, rel: "alternate", type: "text/html"
+ xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.any?
- @issues.each do |issue|
- issue_to_atom(xml, issue)
- end
+ xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
end
-
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index c66b82bb484..b68bf444d27 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -6,7 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id group_url(@group)
xml.updated @events[0].updated_at.xmlschema if @events[0]
- @events.each do |event|
- event_to_atom(xml, event)
- end
+ xml << render(@events) if @events.any?
end
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index e3a356b5379..aedb8468eca 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -47,7 +47,7 @@
%td.import-target
= repo["path_with_namespace"]
%td.import-actions.job-status
- = button_tag class: "btn js-add-to-import" do
+ = button_tag class: "btn btn-import js-add-to-import" do
Import
= icon("spinner spin", class: "loading-icon")
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
new file mode 100644
index 00000000000..68a2d19e58d
--- /dev/null
+++ b/app/views/issues/_issue.atom.builder
@@ -0,0 +1,14 @@
+xml.entry do
+ xml.id namespace_project_issue_url(issue.project.namespace, issue.project, issue)
+ xml.link href: namespace_project_issue_url(issue.project.namespace, issue.project, issue)
+ xml.title truncate(issue.title, length: 80)
+ xml.updated issue.created_at.xmlschema
+ xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
+
+ xml.author do |author|
+ xml.name issue.author_name
+ xml.email issue.author_email
+ end
+
+ xml.summary issue.title
+end
diff --git a/app/views/kaminari/gitlab/_first_page.html.haml b/app/views/kaminari/gitlab/_first_page.html.haml
index ada7306d98d..e7a70e3bb28 100644
--- a/app/views/kaminari/gitlab/_first_page.html.haml
+++ b/app/views/kaminari/gitlab/_first_page.html.haml
@@ -2,7 +2,7 @@
-# available local variables
-# url: url to the first page
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%li.first
diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml
index 3ffd12f8587..80ca30f36e6 100644
--- a/app/views/kaminari/gitlab/_gap.html.haml
+++ b/app/views/kaminari/gitlab/_gap.html.haml
@@ -1,7 +1,7 @@
-# Non-link tag that stands for skipped pages...
-# available local variables
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%li{class: "page"}
diff --git a/app/views/kaminari/gitlab/_last_page.html.haml b/app/views/kaminari/gitlab/_last_page.html.haml
index 3431d029bcc..53f780d1d1b 100644
--- a/app/views/kaminari/gitlab/_last_page.html.haml
+++ b/app/views/kaminari/gitlab/_last_page.html.haml
@@ -2,7 +2,7 @@
-# available local variables
-# url: url to the last page
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%li.last
diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml
index c805914fc3f..125f09777ba 100644
--- a/app/views/kaminari/gitlab/_next_page.html.haml
+++ b/app/views/kaminari/gitlab/_next_page.html.haml
@@ -2,7 +2,7 @@
-# available local variables
-# url: url to the next page
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
- if current_page.last?
diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml
index a52d883b9a8..522e4d1d05f 100644
--- a/app/views/kaminari/gitlab/_page.html.haml
+++ b/app/views/kaminari/gitlab/_page.html.haml
@@ -3,7 +3,7 @@
-# page: a page object for "this" page
-# url: url to this page
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%li{class: "page#{' active' if page.current?}"}
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index a12c53bcfe7..f5e0d2ed3f3 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -1,7 +1,7 @@
-# The container tag
-# available local variables
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside
@@ -9,7 +9,7 @@
%div.gl-pagination
%ul.pagination.clearfix
- unless current_page.first?
- = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages
+ = first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages
= prev_page_tag
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
@@ -18,5 +18,5 @@
= gap_tag
= next_page_tag
- unless current_page.last?
- = last_page_tag unless num_pages < 5
+ = last_page_tag unless total_pages < 5
diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml
index afb20455e0a..7edf10498a8 100644
--- a/app/views/kaminari/gitlab/_prev_page.html.haml
+++ b/app/views/kaminari/gitlab/_prev_page.html.haml
@@ -2,7 +2,7 @@
-# available local variables
-# url: url to the previous page
-# current_page: a page object for the currently displayed page
--# num_pages: total number of pages
+-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
- if current_page.first?
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 79cdbac1f37..b30fb0a5da9 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,6 +30,9 @@
= javascript_include_tag "application"
+ - if page_specific_javascripts
+ = javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true}
+
= csrf_meta_tags
= include_gon
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 3c3bc41bf0e..1e961853c70 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,5 +1,4 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
- = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
@@ -26,7 +25,8 @@
.layout-nav
.container-fluid
= render "layouts/nav/#{nav}"
- .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) }
+ .content-wrapper{ class: "#{layout_nav_class}" }
+ = render "layouts/broadcast"
= render "layouts/flash"
= yield :flash_message
%div{ class: (container_class unless @no_container) }
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 6b208c3d0bb..b49207fc315 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -6,11 +6,8 @@
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
- .search-location-badge
- - if label.present?
- %span.location-badge
- %i.location-text
- = label
+ - if label.present?
+ .location-badge= label
.search-input-wrap
.dropdown{ data: {url: search_autocomplete_path } }
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
diff --git a/app/views/layouts/devise_mailer.html.haml b/app/views/layouts/devise_mailer.html.haml
new file mode 100644
index 00000000000..c258eafdd51
--- /dev/null
+++ b/app/views/layouts/devise_mailer.html.haml
@@ -0,0 +1,34 @@
+!!! 5
+%html
+ %head
+ %meta(content='text/html; charset=UTF-8' http-equiv='Content-Type')
+ = stylesheet_link_tag 'mailers/devise'
+
+ %body
+ %table#wrapper
+ %tr
+ %td
+ %table#header
+ %td{valign: "top"}
+ = image_tag('mailers/gitlab_header_logo.png', id: 'logo', alt: 'GitLab Wordmark')
+
+ %table#body
+ %tr
+ %td#body-container
+ = yield
+
+ - if Gitlab.com?
+ %table#footer
+ %tr
+ %td#tanuki
+ = image_tag('mailers/gitlab_tanuki_2x.png', alt: 'GitLab Logo')
+ %tr
+ %td#tagline
+ Everyone can contribute
+ %tr
+ %td#social
+ = link_to 'Blog', 'https://about.gitlab.com/blog/'
+ = link_to 'Twitter', 'https://twitter.com/gitlab'
+ = link_to 'Facebook', 'https://www.facebook.com/gitlab/'
+ = link_to 'YouTube', 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg'
+ = link_to 'LinkedIn', 'https://www.linkedin.com/company/gitlab-com'
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 86930d4eaaf..c33740e23fa 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,9 +1,12 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
- %button.navbar-toggle{type: 'button'}
+ %button.side-nav-toggle{type: 'button'}
%span.sr-only Toggle navigation
= icon('bars')
+ %button.navbar-toggle{type: 'button'}
+ %span.sr-only Toggle navigation
+ = icon('angle-left')
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -24,8 +27,9 @@
%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
+ - unless todos_pending_count == 0
+ %span.badge.todos-pending-count
+ = todos_pending_count
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 280a1b93729..f292730fe45 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -41,6 +41,11 @@
= icon('file-text fw')
%span
Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ = icon('medkit fw')
+ %span
+ Health Check
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index fad4224e945..306ebd5fcf7 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
- = link_to dashboard_projects_path, title: 'Projects' do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= icon('bookmark fw')
%span
Projects
@@ -11,7 +11,7 @@
Todos
%span.count.todos-pending-count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
%span
Activity
@@ -26,17 +26,17 @@
%span
Milestones
= nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
- %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
+ %span.count= number_with_delimiter(current_user.assigned_open_issues_count)
= nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
- %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
+ %span.count= number_with_delimiter(current_user.assigned_open_merge_request_count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 3438005863a..de15add3617 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,37 +1,40 @@
-= render 'layouts/nav/group_settings'
+%div{ class: nav_control_class }
+ = 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')
- %span
- Group
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- = icon('dashboard fw')
- %span
- Activity
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- = icon('clock-o fw')
- %span
- Milestones
- = nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group), title: 'Issues' do
- = icon('exclamation-circle fw')
- %span
- Issues
- - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %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.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
+ %ul.nav-links.scrolling-tabs
+ .fade-left
+ = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
+ = link_to group_path(@group), title: 'Home' do
+ = icon('group fw')
+ %span
+ Group
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ = icon('dashboard fw')
+ %span
+ Activity
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ = icon('clock-o fw')
+ %span
+ Milestones
+ = nav_link(path: 'groups#issues') do
+ = link_to issues_group_path(@group), title: 'Issues' do
+ = icon('exclamation-circle fw')
+ %span
+ Issues
+ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ %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.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
+ .fade-right
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index e391ec7f2b7..0b2673f1a82 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,7 +1,7 @@
- if current_user
- if access = @group.users.find_by(id: current_user.id)
.controls
- %span.dropdown.group-settings-dropdown
+ .dropdown.group-settings-dropdown
%a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
= icon('cog')
= icon('caret-down')
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index d730840d63a..2efc6c48a48 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,4 +1,5 @@
-%ul.nav-links
+%ul.nav-links.scrolling-tabs
+ .fade-left
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
@@ -47,3 +48,4 @@
= icon('history fw')
%span
Audit Log
+ .fade-right
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 479bde33719..9792c1c93b4 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,131 +1,131 @@
-%ul.nav.nav-sidebar
- - if @project.group
- = nav_link do
- = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
- = icon('caret-square-o-left fw')
+- if current_user
+ .controls
+ - access = user_max_access_in_project(current_user.id, @project)
+ - can_edit = can?(current_user, :admin_project, @project)
+ .dropdown.project-settings-dropdown
+ %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ = render 'layouts/nav/project_settings'
+ %li.divider
+ - if can_edit
+ %li
+ = link_to edit_project_path(@project) do
+ Edit Project
+ - if access
+ %li
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
+ Leave Project
+
+%div{ class: nav_control_class }
+ %ul.nav-links.scrolling-tabs
+ .fade-left
+ = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+ = icon('bookmark fw')
%span
- Go to group
- - else
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
+ Project
+ = nav_link(path: 'projects#activity') do
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+ = icon('dashboard fw')
%span
- Go to dashboard
-
- %li.separate-item
-
- = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
- = icon('bookmark fw')
- %span
- Project
- = nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
- = icon('dashboard fw')
- %span
- Activity
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
- = icon('files-o fw')
- %span
- Files
-
- - if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
- = icon('history fw')
- %span
- Commits
+ Activity
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
+ = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
+ = icon('code fw')
+ %span
+ Code
+
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipelines) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ = icon('ship fw')
+ %span
+ Pipelines
+
+ - if project_nav_tab? :container_registry
+ = nav_link(controller: %w(container_registry)) do
+ = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ = icon('hdd-o fw')
+ %span
+ Container Registry
+
+ - if project_nav_tab? :graphs
+ = nav_link(controller: %w(graphs)) do
+ = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
+ = icon('area-chart fw')
+ %span
+ Graphs
+
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = icon('clock-o fw')
+ %span
+ Milestones
+
+ - if project_nav_tab? :issues
+ = nav_link(controller: :issues) do
+ = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
+ = icon('exclamation-circle fw')
+ %span
+ Issues
+ - if @project.default_issues_tracker?
+ %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
+
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ = icon('tasks fw')
+ %span
+ Merge Requests
+ %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
+
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = icon('tags fw')
+ %span
+ Labels
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+ = icon('book fw')
+ %span
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ = icon('clipboard fw')
+ %span
+ Snippets
+
+ -# Global shortcut to network page for compatibility
+ - if project_nav_tab? :network
+ %li.hidden
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ Network
+
+ -# Shortcut to create a new issue
+ %li.hidden
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ Create a new issue
- - if project_nav_tab? :builds
- = nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
- = icon('cubes fw')
- %span
+ -# Shortcut to builds page
+ - if project_nav_tab? :builds
+ %li.hidden
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
Builds
- %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
-
- - if project_nav_tab? :graphs
- = nav_link(controller: %w(graphs)) do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
- = icon('area-chart fw')
- %span
- Graphs
-
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
- = icon('clock-o fw')
- %span
- Milestones
-
- - if project_nav_tab? :issues
- = nav_link(controller: :issues) do
- = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
- = icon('exclamation-circle fw')
- %span
- Issues
- - if @project.default_issues_tracker?
- %span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
-
- - if project_nav_tab? :merge_requests
- = nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
- = icon('tasks fw')
- %span
- Merge Requests
- %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
-
- - if project_nav_tab? :team
- = nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
- = icon('users fw')
- %span
- Members
-
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
- = icon('tags fw')
- %span
- Labels
-
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
- = icon('book fw')
- %span
- Wiki
- - if project_nav_tab? :forks
- = nav_link(controller: :forks, action: :index) do
- = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks' do
- = icon('code-fork fw')
- %span
- Forks
-
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
- = icon('clipboard fw')
- %span
- Snippets
-
- - if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
- = link_to edit_project_path(@project), title: 'Settings' do
- = icon('cogs fw')
- %span
- Settings
-
- -# Global shortcut to network page for compatibility
- - if project_nav_tab? :network
- %li.hidden
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
- Network
+ -# Shortcut to commits page
+ - if project_nav_tab? :commits
+ %li.hidden
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+ Commits
- -# Shortcut to create a new issue
- %li.hidden
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
- Create a new issue
+ .fade-right
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index d429a928464..885e78d38c6 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,63 +1,45 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to project_path(@project), title: 'Go to project', class: 'back-link' do
- = icon('caret-square-o-left fw')
+- if project_nav_tab? :team
+ = nav_link(controller: [:project_members, :teams]) do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span
- Go to project
+ Members
- %li.separate-item
-
- %ul.sidebar-subnav
- = nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), title: 'Project Settings' do
- = icon('pencil-square-o fw')
- %span
- Project Settings
- - if @project.allowed_to_share_with_group?
- = nav_link(controller: :group_links) do
- = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
- = icon('share-square-o fw')
- %span
- Groups
- = nav_link(controller: :deploy_keys) do
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
- = icon('key fw')
- %span
- Deploy Keys
- = nav_link(controller: :hooks) do
- = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do
- = icon('link fw')
- %span
- Webhooks
- = nav_link(controller: :services) do
- = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
- = icon('cogs fw')
- %span
- Services
- = nav_link(controller: :protected_branches) do
- = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
- = icon('lock fw')
- %span
- Protected Branches
+- if @project.allowed_to_share_with_group?
+ = nav_link(controller: :group_links) do
+ = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
+ %span
+ Groups
+= nav_link(controller: :deploy_keys) do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
+ %span
+ Deploy Keys
+= nav_link(controller: :hooks) do
+ = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do
+ %span
+ Webhooks
+= nav_link(controller: :services) do
+ = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
+ %span
+ Services
+= nav_link(controller: :protected_branches) do
+ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
+ %span
+ Protected Branches
- - if @project.builds_enabled?
- = nav_link(controller: :runners) do
- = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
- = icon('cog fw')
- %span
- Runners
- = nav_link(controller: :variables) do
- = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
- = icon('code fw')
- %span
- Variables
- = nav_link(controller: :triggers) do
- = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
- = icon('retweet fw')
- %span
- Triggers
- = nav_link(controller: :badges) do
- = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
- = icon('star-half-empty fw')
- %span
- Badges
+- if @project.builds_enabled?
+ = nav_link(controller: :runners) do
+ = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
+ %span
+ Runners
+ = nav_link(controller: :variables) do
+ = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
+ %span
+ Variables
+ = nav_link(controller: :triggers) do
+ = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
+ %span
+ Triggers
+ = nav_link(controller: :badges) do
+ = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
+ %span
+ Badges
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 2997f59d946..dde2e2889dc 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -4,6 +4,7 @@
%title
GitLab
= stylesheet_link_tag 'notify'
+ = yield :head
%body
%div.content
= yield
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 6dfe7fbdae8..20d6cdf7246 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,7 +1,7 @@
- page_title @project.name_with_namespace
- page_description @project.description unless page_description
- header_title project_title(@project) unless header_title
-- sidebar "project" unless sidebar
+- nav "project"
- content_for :scripts_body_top do
- project = @target_project || @project
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index 59ce38f67bb..4bc94bd132d 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -1,5 +1,4 @@
- page_title "Settings"
-- header_title project_title(@project, "Settings", edit_project_path(@project))
-- sidebar "project_settings"
+- nav "project"
= render template: "layouts/project"
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 12ded41fbf2..e9c66170877 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -2,4 +2,4 @@
%div
#{link_to @note.author_name, user_url(@note.author)} wrote:
%div
- = markdown(@note.note, pipeline: :email)
+ = markdown(@note.note, pipeline: :email, author: @note.author)
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index ad3ab2525bb..f42b150c0d6 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -2,7 +2,7 @@
%div
#{link_to @issue.author_name, user_url(@issue.author)} wrote:
-if @issue.description
- = markdown(@issue.description, pipeline: :email)
+ = markdown(@issue.description, pipeline: :email, author: @issue.author)
- if @issue.assignee_id.present?
%p
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 23423e7d981..158404de396 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -9,4 +9,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description
- = markdown(@merge_request.description, pipeline: :email)
+ = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author)
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index 65f0e4c4068..a3643a00cfe 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,7 +1,7 @@
-- if @note.diff_file_name
+- if @note.legacy_diff_note?
%p.details
New comment on diff for
- = link_to @note.diff_file_name, @target_url
+ = link_to @note.diff_file_path, @target_url
\:
= render 'note_message'
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index f2e405b14fd..f1532371b2e 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,3 +1,6 @@
+= content_for :head do
+ = stylesheet_link_tag 'mailers/repository_push_email'
+
%h3
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
@@ -43,26 +46,38 @@
= diff.new_path
- unless @message.disable_diffs?
- %h4 Changes:
- - @message.diffs.each_with_index do |diff, i|
- %li{id: "diff-#{i}"}
- %a{href: @message.target_url + "#diff-#{i}"}
- - if diff.deleted_file
- %strong
- = diff.old_path
- deleted
- - elsif diff.renamed_file
- %strong
- = diff.old_path
- &rarr;
- %strong
- = diff.new_path
- - else
- %strong
- = diff.new_path
- %hr
- = color_email_diff(diff.diff)
- %br
+ - diff_files = @message.diffs
- - if @message.compare_timeout
- %h5 Huge diff. To prevent performance issues changes are hidden
+ - if @message.compare_timeout
+ %h5 The diff was not included because it is too large.
+ - else
+ %h4 Changes:
+ - diff_files.each_with_index do |diff_file, i|
+ %li{id: "diff-#{i}"}
+ %a{href: @message.target_url + "#diff-#{i}"}<
+ - if diff_file.deleted_file
+ %strong<
+ = diff_file.old_path
+ deleted
+ - elsif diff_file.renamed_file
+ %strong<
+ = diff_file.old_path
+ &rarr;
+ %strong<
+ = diff_file.new_path
+ - else
+ %strong<
+ = diff_file.new_path
+ - if diff_file.too_large?
+ The diff for this file was not included because it is too large.
+ - else
+ %hr
+ - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last
+ - blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
+ - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
+ %table.code.white
+ - diff_file.highlighted_diff_lines.each do |line|
+ = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true}
+ - else
+ No preview for this file type
+ %br
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 53869e36b28..5ac23aa3997 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -25,24 +25,28 @@
- else
\- #{diff.new_path}
- unless @message.disable_diffs?
- \
- \
- Changes:
- - @message.diffs.each do |diff|
+ - if @message.compare_timeout
\
- \=====================================
- - if diff.deleted_file
- #{diff.old_path} deleted
- - elsif diff.renamed_file
- #{diff.old_path} → #{diff.new_path}
- - else
- = diff.new_path
- \=====================================
- != diff.diff
- - if @message.compare_timeout
- \
- \
- Huge diff. To prevent performance issues it was hidden
+ \
+ The diff was not included because it is too large.
+ - else
+ \
+ \
+ Changes:
+ - @message.diffs.each do |diff_file|
+ \
+ \=====================================
+ - if diff_file.deleted_file
+ #{diff_file.old_path} deleted
+ - elsif diff_file.renamed_file
+ #{diff_file.old_path} → #{diff_file.new_path}
+ - else
+ = diff_file.new_path
+ \=====================================
+ - if diff_file.too_large?
+ The diff for this file was not included because it is too large.
+ - else
+ != diff_file.diff.diff
- if @message.target_url
\
\
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index afd3d79321f..01ac8161945 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -70,7 +70,7 @@
- if current_user.can_change_username?
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0.change-username-title
+ %h4.prepend-top-0.warning-title
Change username
%p
Changing your username will change path to all personal projects!
@@ -94,7 +94,7 @@
- if signup_enabled?
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0.remove-account-title
+ %h4.prepend-top-0.danger-title
Remove account
.col-lg-9
- if @user.can_be_removed?
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index bfe53be6854..1b1b16d656f 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -5,7 +5,7 @@
%h4.prepend-top-0
Application theme
%p
- This setting allows you to customize the appearance of the site, ex. sidebar.
+ This setting allows you to customize the appearance of the site, e.g. the sidebar.
.col-lg-9.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index 0de019983ca..0568c2d305e 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -1,74 +1,65 @@
%fieldset.builds-feature
- %legend
- Builds:
-
+ %h5.prepend-top-0
+ Builds
- unless @repository.gitlab_ci_yml
.form-group
- .col-sm-offset-2.col-sm-10
- %p Builds need to be configured before you can begin using Continuous Integration.
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
- %hr
-
+ %p Builds need to be configured before you can begin using Continuous Integration.
+ = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
.form-group
- .col-sm-offset-2.col-sm-10
- %p Get recent application code using the following command:
- .radio
- = f.label :build_allow_git_fetch_false do
- = f.radio_button :build_allow_git_fetch, 'false'
- %strong git clone
- %br
- %span.descr Slower but makes sure you have a clean dir before every build
- .radio
- = f.label :build_allow_git_fetch_true do
- = f.radio_button :build_allow_git_fetch, 'true'
- %strong git fetch
- %br
- %span.descr Faster
+ %p Get recent application code using the following command:
+ .radio
+ = f.label :build_allow_git_fetch_false do
+ = f.radio_button :build_allow_git_fetch, 'false'
+ %strong git clone
+ %br
+ %span.descr Slower but makes sure you have a clean dir before every build
+ .radio
+ = f.label :build_allow_git_fetch_true do
+ = f.radio_button :build_allow_git_fetch, 'true'
+ %strong git fetch
+ %br
+ %span.descr Faster
.form-group
- = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
- .col-sm-10
- = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
- %p.help-block per build in minutes
+ = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
+ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ %p.help-block per build in minutes
.form-group
- = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
- .col-sm-10
- .input-group
- %span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
- %span.input-group-addon /
- %p.help-block
- We will use this regular expression to find test coverage output in build trace.
- Leave blank if you want to disable this feature
- .bs-callout.bs-callout-info
- %p Below are examples of regex for existing tools:
- %ul
- %li
- Simplecov (Ruby) -
- %code \(\d+.\d+\%\) covered
- %li
- pytest-cov (Python) -
- %code \d+\%\s*$
- %li
- phpunit --coverage-text --colors=never (PHP) -
- %code ^\s*Lines:\s*\d+.\d+\%
- %li
- gcovr (C/C++) -
- %code ^TOTAL.*\s+(\d+\%)$
- %li
- tap --coverage-report=text-summary (Node.js) -
- %code ^Statements\s*:\s*([^%]+)
+ = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ %p.help-block
+ We will use this regular expression to find test coverage output in build trace.
+ Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%\s*$
+ %li
+ phpunit --coverage-text --colors=never (PHP) -
+ %code ^\s*Lines:\s*\d+.\d+\%
+ %li
+ gcovr (C/C++) -
+ %code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :public_builds do
- = f.check_box :public_builds
- %strong Public builds
- .help-block Allow everyone to access builds for Public and Internal projects
+ .checkbox
+ = f.label :public_builds do
+ = f.check_box :public_builds
+ %strong Public builds
+ .help-block Allow everyone to access builds for Public and Internal projects
- .form-group
- = f.label :runners_token, "Runners token", class: 'control-label'
- .col-sm-10
- = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
- %p.help-block The secure token used to checkout project.
+ .form-group.append-bottom-0
+ = f.label :runners_token, "Runners token", class: 'label-light'
+ = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ %p.help-block The secure token used to checkout project.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 9b5de17dd3b..57c3d1b0a65 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,59 +1,37 @@
- empty_repo = @project.empty_repo?
.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
- .project-identicon-holder
- = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
- .cover-title.project-home-desc
- %h1
- = @project.name
- %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
- = visibility_level_icon(@project.visibility_level, fw: false)
-
- - if @project.description.present?
- .cover-desc.project-home-desc
- = markdown(@project.description, pipeline: :description)
-
- - if forked_from_project = @project.forked_from_project
- .cover-desc
- Forked from
- = link_to project_path(forked_from_project) do
- = forked_from_project.namespace.try(:name)
-
- .cover-controls
- - if current_user
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
- = icon('rss')
- - access = user_max_access_in_project(current_user.id, @project)
- - can_edit = can?(current_user, :admin_project, @project)
- - if access || can_edit
- %span.dropdown.project-settings-dropdown
- %a.dropdown-new.btn.btn-gray#project-settings-button{href: '#', 'data-toggle' => 'dropdown'}
- = icon('cog')
- = icon('angle-down')
- %ul.dropdown-menu.dropdown-menu-right
- - if can_edit
- %li
- = link_to edit_project_path(@project) do
- Edit Project
- - if access
- %li
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
- data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
- Leave Project
-
- .project-repo-buttons
- .split-one.count-buttons
- = render 'projects/buttons/star'
- = render 'projects/buttons/fork'
-
- .clone-row
- .project-clone-holder
- = render "shared/clone_panel"
-
- .split-repo-buttons
- .btn-group.pull-left
- = render "projects/buttons/download"
- = render 'projects/buttons/dropdown'
-
+ .container-fluid.container-limited
+ .row
+ .project-image-container
+ = project_icon(@project, alt: '', class: 'project-avatar avatar s70')
+ .project-info
+ .cover-title.project-home-desc
+ %h1
+ = @project.name
+ %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
+ = visibility_level_icon(@project.visibility_level, fw: false)
+
+ - if @project.description.present?
+ .cover-desc.project-home-desc
+ = markdown(@project.description, pipeline: :description)
+
+ - if forked_from_project = @project.forked_from_project
+ .cover-desc
+ Forked from
+ = link_to project_path(forked_from_project) do
+ = forked_from_project.namespace.try(:name)
+
+ .project-repo-buttons
+ .count-buttons
+ = render 'projects/buttons/star'
+ = render 'projects/buttons/fork'
+
+ .project-clone-holder
+ = render "shared/clone_panel"
+
+ .project-repo-buttons.btn-group.project-right-buttons
+ = render "projects/buttons/download"
+ = render 'projects/buttons/dropdown'
= render 'projects/buttons/notifications'
:javascript
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 8de44a6c914..81afea2c60a 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -8,7 +8,7 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
%li.pull-right
- %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
+ %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
Go full screen
.md-write-holder
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index e1e35013968..413477a2d3a 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -4,5 +4,5 @@
= f.text_area attr, class: classes, placeholder: placeholder
- else
= text_area_tag attr, nil, class: classes, placeholder: placeholder
- %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
+ %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress')
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 69fa4ad37c4..3c0f01cbf6f 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,5 +1,4 @@
- page_title "Activity"
-- header_title project_title(@project, "Activity", activity_project_path(@project))
= render 'projects/last_push'
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 49f95ff37db..ede01dcc1aa 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,5 +1,4 @@
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
-= render 'projects/builds/header_title'
.top-block.row-content-block.clearfix
.pull-right
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
index c22384ddf46..ee63bc55a30 100644
--- a/app/views/projects/badges/index.html.haml
+++ b/app/views/projects/badges/index.html.haml
@@ -1,6 +1,5 @@
- page_title 'Badges'
- badges_path = namespace_project_badges_path(@project.namespace, @project)
-- header_title project_title(@project, 'Badges', badges_path)
.prepend-top-10
.panel.panel-default
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 5f9a92ff93f..377665b096f 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Blame", @blob.path, @ref
-- header_title project_title(@project, "Files", project_files_path(@project))
%h3.page-title Blame view
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index fefa652a3da..4071b59c003 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -16,6 +16,9 @@
.license-selector.js-license-selector.hide
= select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name}
+ .gitignore-selector.hidden
+ = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { filenames: gitignore_names } } )
+
.encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
diff --git a/app/views/projects/blob/_header_title.html.haml b/app/views/projects/blob/_header_title.html.haml
deleted file mode 100644
index 78c5ef20a5f..00000000000
--- a/app/views/projects/blob/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Files", project_files_path(@project))
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index effcce5a1c4..e4f04ca7764 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @blob.path, @ref
-= render "header_title"
.file-editor
%ul.nav-links.no-bottom.js-edit-mode
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 0459699432e..c952bc7e5db 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New File", @path.presence, @ref
-= render "header_title"
%h3.page-title
New File
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 6988039b6c7..ed670dae88d 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,5 +1,4 @@
- page_title @blob.path, @ref
-= render "header_title"
= render 'projects/last_push'
diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml
deleted file mode 100644
index a21ddaf4930..00000000000
--- a/app/views/projects/branches/destroy.js.haml
+++ /dev/null
@@ -1 +0,0 @@
-$('.js-totalbranch-count').html("#{@repository.branch_count}")
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ac7790421a4..08148b1a18b 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Branches"
-= render "projects/commits/header_title"
= render "projects/commits/head"
.row-content-block
.pull-right
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index c659af6338c..5a6c8c243fa 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Branch"
-= render "projects/commits/header_title"
- if @error
.alert.alert-danger
diff --git a/app/views/projects/builds/_header_title.html.haml b/app/views/projects/builds/_header_title.html.haml
deleted file mode 100644
index 082dab1f5b0..00000000000
--- a/app/views/projects/builds/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Builds", project_builds_path(@project))
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 2e8015d119b..818d5d28f04 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,5 +1,5 @@
- page_title "Builds"
-= render "header_title"
+= render "projects/pipelines/head"
.top-area
%ul.nav-links
@@ -35,9 +35,6 @@
= icon('wrench')
%span CI Lint
-.row-content-block
- #{(@scope || 'running').capitalize} builds from this project
-
%ul.content-list
- if @builds.blank?
%li
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index c0f7a7686f0..16017c994ba 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,5 +1,5 @@
- page_title "#{@build.name} (##{@build.id})", "Builds"
-= render "header_title"
+- trace_with_state = @build.trace_with_state
.build-page
.row-content-block.top-block
@@ -85,7 +85,9 @@
%pre.trace#build-trace
%code.bash
= preserve do
- = raw @build.trace_html
+ = raw trace_with_state[:html]
+ - if @build.active?
+ %i{:class => "fa fa-refresh fa-spin"}
%div#down-build-trace
@@ -216,4 +218,4 @@
:javascript
- new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}")
+ new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 1e4c46fca2f..16b8e1cca91 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -2,7 +2,7 @@
.btn-group
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
- %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
+ %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
- can_create_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- can_create_snippet = can?(current_user, :create_snippet, @project)
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 5fb5fe5af2f..34ad9fe2c43 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -12,7 +12,8 @@
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
- = link_to namespace_project_forks_path(@project.namespace, @project), class: 'count-with-arrow' do
+ %div.count-with-arrow
%span.arrow
%span.count
- = @project.forks_count
+ = link_to namespace_project_forks_path(@project.namespace, @project) do
+ = @project.forks_count
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index c1e3e5b73a2..1d05da50581 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -1,11 +1,11 @@
- if @notification_setting
= form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f|
= f.hidden_field :level
- %span.dropdown
+ .dropdown
%a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"}
= icon('bell')
= notification_title(@notification_setting.level)
- = icon('angle-down')
- %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
- NotificationSetting.levels.each do |level|
= notification_list_item(level.first, @notification_setting)
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 8e95f040273..5bd6e3f0ebc 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -13,7 +13,9 @@
%strong ##{build.id}
- if build.stuck?
- %i.fa.fa-warning.text-warning
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+ - if defined?(retried) && retried
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
- if defined?(commit_sha) && commit_sha
%td
@@ -70,11 +72,11 @@
.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', class: 'btn btn-build' do
- %i.fa.fa-download
+ = icon('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', class: 'btn btn-build' do
- %i.fa.fa-remove.cred
+ = icon('remove', class: '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', class: 'btn btn-build' do
- %i.fa.fa-refresh
+ = icon('refresh')
diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml
new file mode 100644
index 00000000000..5e3a4123a8e
--- /dev/null
+++ b/app/views/projects/ci/commits/_commit.html.haml
@@ -0,0 +1,71 @@
+- status = commit.status
+%tr.commit
+ %td.commit-link
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do
+ = ci_icon_for_status(status)
+ %strong ##{commit.id}
+
+ %td
+ %div.branch-commit
+ - if commit.ref
+ = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace"
+ &middot;
+ = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"
+ &nbsp;
+ - if commit.tag?
+ %span.label.label-primary tag
+ - elsif commit.latest?
+ %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
+ - if commit.triggered?
+ %span.label.label-primary triggered
+ - if commit.yaml_errors.present?
+ %span.label.label-danger.has-tooltip{ title: "#{commit.yaml_errors}" } yaml invalid
+ - if commit.builds.any?(&:stuck?)
+ %span.label.label-warning stuck
+
+ %p.commit-title
+ - if commit_data = commit.commit_data
+ = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
+ - else
+ Cant find HEAD commit for this branch
+
+
+ - stages_status = commit.statuses.stages_status
+ - stages.each do |stage|
+ %td
+ - status = stages_status[stage]
+ - tooltip = "#{stage.titleize}: #{status || 'not found'}"
+ - if status
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
+ = ci_icon_for_status(status)
+ - else
+ .light.has-tooltip{ title: tooltip }
+ \-
+
+ %td
+ - if commit.started_at && commit.finished_at
+ %p.duration
+ #{duration_in_words(commit.finished_at, commit.started_at)}
+
+ %td
+ .controls.hidden-xs.pull-right
+ - artifacts = commit.builds.latest.select { |b| b.artifacts? }
+ - if artifacts.present?
+ .dropdown.inline.build-artifacts
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon('download')
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - artifacts.each do |build|
+ %li
+ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
+ = icon("download")
+ %span #{build.name}
+
+ - if can?(current_user, :update_pipeline, @project)
+ - if commit.retryable?
+ = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+ = icon("repeat")
+ - if commit.cancelable?
+ = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ = icon("remove")
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 5c9a319edeb..7f7a15aa214 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,2 +1,2 @@
- @ci_commits.each do |ci_commit|
- = render "ci_commit", ci_commit: ci_commit
+ = render "ci_commit", ci_commit: ci_commit, pipeline_details: true
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
index e849aefb188..32ff4d30977 100644
--- a/app/views/projects/commit/_ci_commit.html.haml
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -1,24 +1,27 @@
.row-content-block.build-content.middle-block
.pull-right
- - if can?(current_user, :update_build, @project)
+ - if can?(current_user, :update_pipeline, ci_commit.project)
- if ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+ = link_to "Retry failed", retry_namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post
- if ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+ = link_to "Cancel running", cancel_namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
- .oneline
- = pluralize ci_commit.statuses.count(:id), "build"
- - if ci_commit.ref
- for
- %span.label.label-info
- = ci_commit.ref
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
- - if ci_commit.duration
- in
- = time_interval_in_words ci_commit.duration
+ .oneline.clearfix
+ - if defined?(pipeline_details) && pipeline_details
+ Pipeline
+ = link_to "##{ci_commit.id}", namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), class: "monospace"
+ with
+ = pluralize ci_commit.statuses.count(:id), "build"
+ - if ci_commit.ref
+ for
+ = link_to ci_commit.ref, namespace_project_commits_path(ci_commit.project.namespace, ci_commit.project, ci_commit.ref), class: "monospace"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to ci_commit.short_sha, namespace_project_commit_path(ci_commit.project.namespace, ci_commit.project, ci_commit.sha), class: "monospace"
+ - if ci_commit.duration
+ in
+ = time_interval_in_words ci_commit.duration
- if ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
@@ -28,7 +31,7 @@
%li= error
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-- if @project.builds_enabled? && !ci_commit.ci_yaml_file
+- if ci_commit.project.builds_enabled? && !ci_commit.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
@@ -38,34 +41,12 @@
%tr
%th Status
%th Build ID
- %th Stage
%th Name
%th Tags
%th Duration
%th Finished at
- - if @project.build_coverage_enabled?
+ - if ci_commit.project.build_coverage_enabled?
%th Coverage
%th
- - builds = ci_commit.statuses.latest.ordered
- = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
-
-- if ci_commit.retried.any?
- .row-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Tags
- %th Duration
- %th Finished at
- - if @project.build_coverage_enabled?
- %th Coverage
- %th
- = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
+ - ci_commit.statuses.stages.each do |stage|
+ = render 'projects/commit/ci_stage', stage: stage, statuses: ci_commit.statuses.where(stage: stage)
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
new file mode 100644
index 00000000000..ae7bb01223e
--- /dev/null
+++ b/app/views/projects/commit/_ci_stage.html.haml
@@ -0,0 +1,15 @@
+%tr
+ %th{colspan: 10}
+ %strong
+ %a{name: stage}
+ - status = statuses.latest.status
+ %span{class: "ci-status-link ci-status-icon-#{status}"}
+ = ci_icon_for_status(status)
+ - if stage
+ &nbsp;
+ = stage.titleize.pluralize
+ = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
+ = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
+ %tr
+ %td{colspan: 10}
+ &nbsp;
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 01163e526b2..6674d58417b 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,37 +1,35 @@
-.pull-right.commit-action-buttons
- %div
- - if @notes_count > 0
- %span.btn.disabled.btn-grouped
- %i.fa.fa-comment
+.commit-info-row.commit-info-row-header
+ %span.hidden-xs Authored by
+ %strong
+ = commit_author_link(@commit, avatar: true, size: 24)
+ #{time_ago_with_tooltip(@commit.authored_date)}
+
+ .pull-right.commit-action-buttons
+ - if defined?(@notes_count) && @notes_count > 0
+ %span.btn.disabled.btn-grouped.hidden-xs
+ = icon('comment')
= @notes_count
- .pull-left.btn-group
- %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.fa.fa-download
- Download as
- %span.caret
- %ul.dropdown-menu
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped hidden-xs hidden-sm" do
+ Browse Files
+ .dropdown.inline
+ %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
+ %span.hidden-xs Options
+ %span.caret.commit-options-dropdown-caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li.visible-xs-block.visible-sm-block
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
+ Browse Files
+ - unless @commit.has_been_reverted?(current_user)
+ %li.clearfix
+ = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ %li.clearfix
+ = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ %li.divider
+ %li.dropdown-header
+ Download
- unless @commit.parents.length > 1
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
- = icon('files-o')
- Browse Files
- - unless @commit.has_been_reverted?(current_user)
- = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
- = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
- %div
-
-%p
-.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)
- #{time_ago_with_tooltip(@commit.authored_date)}
- if @commit.different_committer?
.commit-info-row
@@ -41,8 +39,9 @@
#{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"
+ %span.hidden-xs.hidden-sm Commit
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace hidden-xs hidden-sm"
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace visible-xs-inline visible-sm-inline"
= clipboard_button(clipboard_text: @commit.id)
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
@@ -51,12 +50,23 @@
%span.commit-info.branches
%i.fa.fa-spinner.fa-spin
+- if @commit.status
+ .commit-info-row
+ Builds for
+ = pluralize(@commit.ci_commits.count, 'pipeline')
+ = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do
+ = ci_icon_for_status(@commit.status)
+ = ci_label_for_status(@commit.status)
+ - if @commit.ci_commits.duration
+ in
+ = time_interval_in_words @commit.ci_commits.duration
+
.commit-box.content-block
%h3.commit-title
- = markdown escape_once(@commit.title), pipeline: :single_line
+ = markdown escape_once(@commit.title), pipeline: :single_line, author: @commit.author
- if @commit.description.present?
%pre.commit-description
- = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
+ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line, author: @commit.author))
:javascript
$(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 7118a4846c6..2f051fb90e0 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -1,7 +1,7 @@
- page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits"
-= render "projects/commits/header_title"
+
.prepend-top-default
= render "commit_box"
-= render "ci_menu"
+= render "ci_menu"
= render "builds"
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index e5e3d696035..401cb4f7e30 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,8 +1,6 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
-= render "projects/commits/header_title"
-
.prepend-top-default
= render "commit_box"
- if @commit.status
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
new file mode 100644
index 00000000000..1657fb46163
--- /dev/null
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -0,0 +1,14 @@
+xml.entry do
+ xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id)
+ xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
+ xml.title truncate(commit.title, length: 80)
+ xml.updated commit.committed_date.xmlschema
+ xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
+
+ xml.author do |author|
+ xml.name commit.author_name
+ xml.email commit.author_email
+ end
+
+ xml.summary markdown(commit.description, pipeline: :single_line)
+end
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index c7d8c9a0d15..367027182b6 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -17,14 +17,14 @@
.pull-right
- if commit.status
- = render_ci_status(commit)
+ = render_commit_status(commit)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
- if commit.description?
.commit-row-description.js-toggle-content
%pre
- = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
+ = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
.commit-row-info
by
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 64e8da9201d..7283a78a64e 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -3,7 +3,7 @@
- commits, hidden = limited_commits(@commits)
-- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
+- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
.row.commits-row
.col-md-2.hidden-xs.hidden-sm
%h5.commits-row-date
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index d1bd76ab529..1c136133ab0 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,9 +1,11 @@
%ul.nav-links
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_files_path(@project) do
+ Files
+
= 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)
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
@@ -16,9 +18,7 @@
= nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do
Branches
- %span.badge.js-totalbranch-count= @repository.branch_count
= nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
- %span.badge.js-totaltags-count= @repository.tag_count
diff --git a/app/views/projects/commits/_header_title.html.haml b/app/views/projects/commits/_header_title.html.haml
deleted file mode 100644
index e4385893dd9..00000000000
--- a/app/views/projects/commits/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Commits", project_commits_path(@project))
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index e310fafd82c..30bb7412073 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -6,18 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
xml.updated @commits.first.committed_date.xmlschema if @commits.any?
- @commits.each do |commit|
- xml.entry do
- xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id)
- xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
- xml.title truncate(commit.title, length: 80)
- xml.updated commit.committed_date.xmlschema
- xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
- xml.author do |author|
- xml.name commit.author_name
- xml.email commit.author_email
- end
- xml.summary markdown(commit.description, pipeline: :single_line)
- end
- end
+ xml << render(@commits) if @commits.any?
end
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 088eaa28013..2c21923ed4f 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Commits", @ref
-= render "header_title"
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 5e188dd0f3c..0b8ed23b305 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Compare"
-= render "projects/commits/header_title"
= render "projects/commits/head"
.row-content-block
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 62525168239..cdc34f51d6d 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,5 +1,4 @@
- page_title "#{params[:from]}...#{params[:to]}"
-= render "projects/commits/header_title"
= render "projects/commits/head"
diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml
new file mode 100644
index 00000000000..4e9f936539b
--- /dev/null
+++ b/app/views/projects/container_registry/_tag.html.haml
@@ -0,0 +1,21 @@
+%tr.tag
+ %td
+ = escape_once(tag.name)
+ = clipboard_button(clipboard_text: "docker pull #{tag.path}")
+ %td
+ - if layer = tag.layers.first
+ %span.has-tooltip{ title: "#{layer.revision}" }
+ = layer.short_revision
+ - else
+ \-
+ %td
+ = number_to_human_size(tag.total_size)
+ &middot;
+ = pluralize(tag.layers.size, "layer")
+ %td
+ = time_ago_in_words(tag.created_at)
+ - if can?(current_user, :update_container_image, @project)
+ %td.content
+ .controls.hidden-xs.pull-right
+ = link_to namespace_project_container_registry_path(@project.namespace, @project, tag.name), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do
+ = icon("trash cred")
diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml
new file mode 100644
index 00000000000..993da27310f
--- /dev/null
+++ b/app/views/projects/container_registry/index.html.haml
@@ -0,0 +1,39 @@
+- page_title "Container Registry"
+
+%hr
+
+%ul.content-list
+ %li.light.prepend-top-default
+ %p
+ A 'container image' is a snapshot of a container.
+ You can host your container images with GitLab.
+ %br
+ To start using container images hosted on GitLab you first need to login:
+ %pre
+ %code
+ docker login #{Gitlab.config.registry.host_port}
+ %br
+ Then you are free to create and upload a container image with build and push commands:
+ %pre
+ docker build -t #{escape_once(@project.container_registry_repository_url)} .
+ %br
+ docker push #{escape_once(@project.container_registry_repository_url)}
+
+ - if @tags.blank?
+ %li
+ .nothing-here-block No images in Container Registry for this project.
+
+ - else
+ .table-holder
+ %table.table.tags
+ %thead
+ %tr
+ %th Name
+ %th Image ID
+ %th Size
+ %th Created
+ - if can?(current_user, :update_container_image, @project)
+ %th
+
+ - @tags.each do |tag|
+ = render 'tag', tag: tag
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index e230834e8ba..04fbb37d93f 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -19,7 +19,7 @@
%ul.well-list
= render @enabled_keys
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
No deploy keys found. Create one with the form above or add existing one below.
%h5.prepend-top-default
Deploy keys from projects you have access to (#{@available_project_keys.size})
@@ -27,7 +27,7 @@
%ul.well-list
= render @available_project_keys
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- if @available_public_keys.any?
%h5.prepend-top-default
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 0f04fc5d33c..e5983c58039 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -11,11 +11,9 @@
= link_to "#diff-#{i}" do
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- .filename.old
- = old_path
+ = old_path
&rarr;
- .filename.new
- = new_path
+ = new_path
- else
%span
= diff_file.new_path
@@ -41,7 +39,7 @@
.diff-content.diff-wrap-lines
- # Skip all non non-supported blobs
- - return unless blob.respond_to?('text?')
+ - return unless blob.respond_to?(:text?)
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 107097ad963..f1577e8a47b 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -15,7 +15,7 @@
= link_text
- else
= link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- - if @comments_allowed && can?(current_user, :create_note, @project)
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 81948513e43..4ecc9528bd2 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -16,7 +16,7 @@
- else
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- - if @comments_allowed && can?(current_user, :create_note, @project)
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
@@ -29,14 +29,14 @@
%td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- - if @comments_allowed && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(right[:line_code], 'new')
+ - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
+ = link_to_new_diff_note(new_line_code, 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- - if @reply_allowed
- - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
- - if comments_left.present? || comments_right.present?
- = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
+ - unless @diff_notes_disabled
+ - notes_left, notes_right = organize_comments(left, right)
+ - if notes_left.present? || notes_right.present?
+ = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
- if diff_file.diff.diff.blank? && diff_file.mode_changed?
.file-mode-changed
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index e7169d7b599..068593a7dd1 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -6,16 +6,15 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0
- - raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- line_code = generate_line_code(diff_file.file_path, line)
- last_line = line.new_pos
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
- - if @reply_allowed
- - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
- - unless comments.empty?
- = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
+ - unless @diff_notes_disabled
+ - diff_notes = @grouped_diff_notes[line_code]
+ - if diff_notes
+ = render "projects/notes/diff_notes_with_reply", notes: diff_notes
- if last_line > 0
= render "projects/diffs/match_line", { line: "",
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 76a4f41193c..18b125ff9d4 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,251 +1,221 @@
-.project-edit-container.prepend-top-default
- .project-edit-errors
- .project-edit-content
- .panel.panel-default
- .panel-heading
+.project-edit-container
+ .row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
Project settings
- .panel-body
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
-
- %fieldset
- .form-group.project_name_holder
- = f.label :name, class: 'control-label' do
- Project name
- .col-sm-10
- = f.text_field :name, class: "form-control", id: "project_name_edit"
-
-
- .form-group
- = f.label :description, class: 'control-label' do
- Project description
- %span.light (optional)
- .col-sm-10
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
-
- - unless @project.empty_repo?
- .form-group
- = f.label :default_branch, "Default Branch", class: 'control-label'
- .col-sm-10= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
-
-
- = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
-
+ .col-lg-9
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
+ %fieldset.append-bottom-0
.form-group
- = f.label :tag_list, "Tags", class: 'control-label'
- .col-sm-10
- = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
- %p.help-block Separate tags with commas.
-
- %fieldset.features
- %legend
- Features:
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :issues_enabled do
- = f.check_box :issues_enabled
- %strong Issues
- %br
- %span.descr Lightweight issue tracking system for this project
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :merge_requests_enabled do
- = f.check_box :merge_requests_enabled
- %strong Merge Requests
- %br
- %span.descr Submit changes to be merged upstream
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :builds_enabled do
- = f.check_box :builds_enabled
- %strong Builds
- %br
- %span.descr Test and deploy your changes before merge
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :wiki_enabled do
- = f.check_box :wiki_enabled
- %strong Wiki
- %br
- %span.descr Pages for project documentation
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :snippets_enabled do
- = f.check_box :snippets_enabled
- %strong Snippets
- %br
- %span.descr Share code pastes with others out of git repository
-
- = render 'builds_settings', f: f
+ = f.label :name, class: 'label-light' do
+ Project name
+ = f.text_field :name, class: "form-control", id: "project_name_edit"
+ .form-group
+ = f.label :description, class: 'label-light' do
+ Project description
+ %span.light (optional)
+ = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- %fieldset.features
- %legend
- Project avatar:
+ - unless @project.empty_repo?
.form-group
- .col-sm-offset-2.col-sm-10
- - if @project.avatar?
- = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
- %p.light
- - if @project.avatar_in_git
- Project avatar in repository: #{ @project.avatar_in_git }
- %p.light
- - if @project.avatar?
- You can change your project avatar here
- - else
- You can upload a project avatar here
- %a.choose-btn.btn.btn-sm.js-choose-project-avatar-button
- %i.icon-paper-clip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-project-avatar-input hidden"
- .light The maximum file size allowed is 200KB.
- - if @project.avatar?
- %hr
- = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
-
-
- .form-actions
- = f.submit 'Save changes', class: "btn btn-save"
-
-
-
- .danger-settings
- .panel.panel-default
- .panel-heading Housekeeping
- .errors-holder
- .panel-body
- %p
- Runs a number of housekeeping tasks within the current repository,
- such as compressing file revisions and removing unreachable objects.
- %br
-
- .form-actions
- = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
- method: :post, class: "btn btn-default"
-
- - if can? current_user, :archive_project, @project
- - if @project.archived?
- .panel.panel-success
- .panel-heading
- Unarchive project
- .panel-body
- %p
- Unarchiving the project will mark its repository as active.
+ = f.label :default_branch, "Default Branch", class: 'label-light'
+ = f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
+ .form-group.project-visibility-level-holder
+ = f.label :visibility_level, class: 'label-light' do
+ Visibility Level
+ = link_to "(?)", help_page_path("public_access", "public_access")
+ - if can_change_visibility_level?(@project, current_user)
+ = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
+ - else
+ .info
+ = visibility_level_icon(@project.visibility_level)
+ %strong
+ = visibility_level_label(@project.visibility_level)
+ .light= visibility_level_description(@project.visibility_level, @project)
+ .form-group
+ = f.label :tag_list, "Tags", class: 'label-light'
+ = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
+ %p.help-block Separate tags with commas.
+ %hr
+ %fieldset.features.append-bottom-0
+ %h5.prepend-top-0
+ Features
+ .form-group
+ .checkbox
+ = f.label :issues_enabled do
+ = f.check_box :issues_enabled
+ %strong Issues
%br
- The project can be committed to.
+ %span.descr Lightweight issue tracking system for this project
+ .form-group
+ .checkbox
+ = f.label :merge_requests_enabled do
+ = f.check_box :merge_requests_enabled
+ %strong Merge Requests
%br
- %strong Once active this project shows up in the search and on the dashboard.
-
- .form-actions
- = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
- method: :post, class: "btn btn-success"
- - else
- .panel.panel-warning
- .panel-heading
- Archive project
- .panel-body
- %p
- Archiving the project will mark its repository as read-only.
+ %span.descr Submit changes to be merged upstream
+ .form-group
+ .checkbox
+ = f.label :builds_enabled do
+ = f.check_box :builds_enabled
+ %strong Builds
+ %br
+ %span.descr Test and deploy your changes before merge
+ .form-group
+ .checkbox
+ = f.label :wiki_enabled do
+ = f.check_box :wiki_enabled
+ %strong Wiki
%br
- It is hidden from the dashboard and doesn't show up in searches.
+ %span.descr Pages for project documentation
+ .form-group
+ .checkbox
+ = f.label :snippets_enabled do
+ = f.check_box :snippets_enabled
+ %strong Snippets
%br
- %strong Archived projects cannot be committed to!
-
- .form-actions
- = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
- data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
- method: :post, class: "btn btn-warning"
- - else
- .nothing-here-block Only the project owner can archive a project
-
- .panel.panel-default.panel.panel-warning
- .panel-heading Rename repository
- .errors-holder
- .panel-body
- = form_for([@project.namespace.becomes(Namespace), @project], html: { class: 'form-horizontal' }) do |f|
- .form-group.project_name_holder
- = f.label :name, class: 'control-label' do
- Project name
- .col-sm-9
- .form-group
- = f.text_field :name, class: "form-control"
+ %span.descr Share code pastes with others out of git repository
+ - if Gitlab.config.registry.enabled
.form-group
- = f.label :path, class: 'control-label' do
- %span Path
- .col-sm-9
- .form-group
- .input-group
- .input-group-addon
- #{URI.join(root_url, @project.namespace.path)}/
- = f.text_field :path, class: 'form-control'
- %ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- .form-actions
- = f.submit 'Rename project', class: "btn btn-warning"
-
- - if can?(current_user, :change_namespace, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Transfer project
- .errors-holder
- .panel-body
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
- .form-group
- = label_tag :new_namespace_id, nil, class: 'control-label' do
- %span Namespace
- .col-sm-9
- .form-group
- = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
- %ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
- %li You will need to update your local repositories to point to the new location.
- %li Project visibility level will be changed to match namespace rules when transfering to a group.
- .form-actions
- = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- - else
- .nothing-here-block Only the project owner can transfer a project
-
- - if @project.forked?
- - if can?(current_user, :remove_fork_project, @project)
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
- .panel.panel-default.panel.panel-danger
- .panel-heading Remove fork relationship
- .panel-body
- %p
- This will remove the fork relationship to source project
- #{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
+ .checkbox
+ = f.label :container_registry_enabled do
+ = f.check_box :container_registry_enabled
+ %strong Container Registry
%br
- %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- .form-actions
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ %span.descr Enable Container Registry for this repository
+ %hr
+ = render 'builds_settings', f: f
+ %hr
+ %fieldset.features.append-bottom-default
+ %h5.prepend-top-0
+ Project avatar
+ .form-group
+ - if @project.avatar?
+ = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ %p.light
+ - if @project.avatar_in_git
+ Project avatar in repository: #{ @project.avatar_in_git }
+ %a.choose-btn.btn.js-choose-project-avatar-button
+ Browse file...
+ %span.file_name.prepend-left-default.js-avatar-filename No file chosen
+ = f.file_field :avatar, class: "js-project-avatar-input hidden"
+ .help-block The maximum file size allowed is 200KB.
+ - if @project.avatar?
+ %hr
+ = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+ = f.submit 'Save changes', class: "btn btn-save"
+ .row.prepend-top-default
+ %hr
+ .row.prepend-top-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Housekeeping
+ %p.append-bottom-0
+ %p
+ Runs a number of housekeeping tasks within the current repository,
+ such as compressing file revisions and removing unreachable objects.
+ .col-lg-9
+ = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
+ method: :post, class: "btn btn-save"
+ %hr
+ - if can? current_user, :archive_project, @project
+ .row.prepend-top-default
+ .col-lg-3
+ %h4.warning-title.prepend-top-0
+ - if @project.archived?
+ Unarchive project
+ - else
+ Archive project
+ %p.append-bottom-0
+ - if @project.archived?
+ Unarchiving the project will mark its repository as active. The project can be committed to.
+ - else
+ Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
+ .col-lg-9
+ - if @project.archived?
+ %p
+ %strong Once active this project shows up in the search and on the dashboard.
+ = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+ method: :post, class: "btn btn-success"
- else
- .nothing-here-block Only the project owner can remove the fork relationship.
-
- - if can?(current_user, :remove_project, @project)
- .panel.panel-default.panel.panel-danger
- .panel-heading Remove project
- .panel-body
- = form_tag(namespace_project_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do
- %p
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- %br
- %strong Removed projects cannot be restored!
- .form-actions
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- - else
- .nothing-here-block Only the project owner can remove a project.
-
+ %p
+ %strong Archived projects cannot be committed to!
+ = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+ data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+ method: :post, class: "btn btn-warning"
+ %hr
+ .row.prepend-top-default
+ .col-lg-3
+ %h4.prepend-top-0.warning-title
+ Rename repository
+ .col-lg-9
+ = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'label-light' do
+ Project name
+ .form-group
+ = f.text_field :name, class: "form-control"
+ .form-group
+ = f.label :path, class: 'label-light' do
+ %span Path
+ .form-group
+ .input-group
+ .input-group-addon
+ #{URI.join(root_url, @project.namespace.path)}/
+ = f.text_field :path, class: 'form-control'
+ %ul
+ %li Be careful. Renaming a project's repository can have unintended side effects.
+ %li You will need to update your local repositories to point to the new location.
+ = f.submit 'Rename project', class: "btn btn-warning"
+ - if can?(current_user, :change_namespace, @project)
+ %hr
+ .row.prepend-top-default
+ .col-lg-3
+ %h4.prepend-top-0.danger-title
+ Transfer project
+ .col-lg-9
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true) do |f|
+ .form-group
+ = label_tag :new_namespace_id, nil, class: 'label-light' do
+ %span Namespace
+ .form-group
+ = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
+ %ul
+ %li Be careful. Changing the project's namespace can have unintended side effects.
+ %li You can only transfer the project to namespaces you manage.
+ %li You will need to update your local repositories to point to the new location.
+ %li Project visibility level will be changed to match namespace rules when transfering to a group.
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ - if @project.forked? && can?(current_user, :remove_fork_project, @project)
+ %hr
+ .row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0.danger-title
+ Remove fork relationship
+ %p.append-bottom-0
+ %p
+ This will remove the fork relationship to source project
+ = succeed "." do
+ = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+ .col-lg-9
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
+ %p
+ %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
+ = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ - if can?(current_user, :remove_project, @project)
+ %hr
+ .row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0.danger-title
+ Remove project
+ %p.append-bottom-0
+ Removing the project will delete its repository and all related resources including issues, merge requests etc.
+ .col-lg-9
+ = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do
+ %p
+ %strong Removed projects cannot be restored!
+ = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
.save-project-loader.hide
.center
@@ -254,5 +224,4 @@
Saving project.
%p Please wait a moment, this page will automatically refresh when ready.
-
= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 1a2e59752fe..636beb73ec2 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -15,10 +15,14 @@
If you already have files you can push them using command line instructions below.
%p
Otherwise you can start with adding a
- = link_to "README", new_readme_path, class: 'underlined-link'
+ = succeed ',' do
+ = link_to "README", new_readme_path, class: 'underlined-link'
+ a
+ = succeed ',' do
+ = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
or a
- = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
- file to this project.
+ = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link'
+ to this project.
- if can?(current_user, :push_code, @project)
%div{ class: container_class }
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 1fe1d98bf13..9322c82904f 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Find File", @ref
-- header_title project_title(@project, "Files", project_files_path(@project))
.file-finder-holder.tree-holder.clearfix
.nav-block
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index f21c864e35c..5bc5c71283e 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -12,6 +12,9 @@
- else
%strong ##{generic_commit_status.id}
+ - if defined?(retried) && retried
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
+
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
@@ -37,11 +40,13 @@
%td
= generic_commit_status.name
- .pull-right
- - if generic_commit_status.tags.any?
- - generic_commit_status.tags.each do |tag|
- %span.label.label-primary
- = tag
+ %td
+ - if generic_commit_status.tags.any?
+ - generic_commit_status.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
%td.duration
- if generic_commit_status.duration
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 79a56647c53..8becaea246f 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,3 +1,4 @@
+- page_specific_javascripts asset_path("graphs/application.js")
%ul.nav-links
= nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path
diff --git a/app/views/projects/graphs/_header_title.html.haml b/app/views/projects/graphs/_header_title.html.haml
deleted file mode 100644
index 1e2f61cd22b..00000000000
--- a/app/views/projects/graphs/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Graphs", namespace_project_graph_path(@project.namespace, @project, current_ref))
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index 9f05be9982b..19ccc125ea8 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -1,5 +1,4 @@
- page_title "Continuous Integration", "Graphs"
-= render "header_title"
= render 'head'
.row-content-block.append-bottom-default
.oneline
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index da9f648cc9c..d9b2fb6c065 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,5 +1,4 @@
- page_title "Commits", "Graphs"
-= render "header_title"
= render 'head'
.row-content-block.append-bottom-default
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index ebecab1dbfc..249c16f4709 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -1,5 +1,4 @@
- page_title "Languages", "Graphs"
-= render "header_title"
= render 'head'
.row-content-block.append-bottom-default
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index ad4a932d391..33970e7b909 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Contributors", "Graphs"
-= render "header_title"
= render 'head'
.row-content-block.append-bottom-default
@@ -19,7 +18,7 @@
.header.clearfix
%h3#date_header.page-title
%p.light
- Commits to #{@ref}, excluding merge commits. Limited by 6,000 commits
+ Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
%input#brush_change{:type => "hidden"}
.graphs
#contributors-master
diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml
index 62eba5888a4..8151187d499 100644
--- a/app/views/projects/hooks/_project_hook.html.haml
+++ b/app/views/projects/hooks/_project_hook.html.haml
@@ -3,7 +3,7 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events wiki_page_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray.deploy-project-label= trigger.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 36c1d69f060..917a0b805b1 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -64,6 +64,13 @@
Build events
%p.light
This url will be triggered when the build status changes
+ %div
+ = f.check_box :wiki_page_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :wiki_page_events, class: 'label-light append-bottom-0' do
+ Wiki Page events
+ %p.light
+ This url will be triggered when a wiki page is created/updated
.form-group
= f.label :enable_ssl_verification, "SSL verification", class: "label-light"
%div
@@ -80,5 +87,5 @@
- @hooks.each do |hook|
= render "project_hook", hook: hook
- else
- %p.profile-settings-message.text-center.append-bottom-0
+ %p.settings-message.text-center.append-bottom-0
No webhooks found, add one in the form above.
diff --git a/app/views/projects/issues/_header_title.html.haml b/app/views/projects/issues/_header_title.html.haml
deleted file mode 100644
index 99f03549c44..00000000000
--- a/app/views/projects/issues/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 9ad86ed71c9..78f64150601 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,7 +6,7 @@
.issue-title.title
%span.issue-title-text
= confidential_icon(issue)
- = link_to_gfm issue.title, issue_path(issue)
+ = link_to issue.title, issue_path(issue)
%ul.controls
- if issue.closed?
%li
@@ -28,16 +28,10 @@
= downvotes
- note_count = issue.notes.user.nonawards.count
- - if note_count > 0
- %li
- = link_to issue_path(issue) + "#notes" do
- = icon('comments')
- = note_count
- - else
- %li
- = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
- = icon('comments')
- = note_count
+ %li
+ = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do
+ = icon('comments')
+ = note_count
.issue-info
#{issue.to_reference} &middot;
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index d6b38b327ff..2f9dc867d0d 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -7,7 +7,7 @@
%li
%span.merge-request-ci-status
- if merge_request.ci_commit
- = render_ci_status(merge_request.ci_commit)
+ = render_pipeline_status(merge_request.ci_commit)
- elsif has_any_ci
= icon('blank fw')
%span.merge-request-id
@@ -24,5 +24,8 @@
MERGED
- elsif merge_request.closed?
CLOSED
- - if @closed_by_merge_requests.present?
+ %li
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
+ - if @closed_by_merge_requests.present?
+ %li
+ = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index bdfa0c7009e..5f9d2919982 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -8,7 +8,7 @@
- ci_commit = @project.ci_commit(sha, branch) if sha
- if ci_commit
%span.related-branch-ci-status
- = render_ci_status(ci_commit)
+ = render_pipeline_status(ci_commit)
%span.related-branch-info
%strong
= link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 20216297d25..7cf1923456e 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
-= render "header_title"
%h3.page-title
Edit Issue ##{@issue.iid}
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index ee8a9414657..7ad7c9c87e8 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -6,7 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.xmlschema if @issues.any?
- @issues.each do |issue|
- issue_to_atom(xml, issue)
- end
+ xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
end
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index efa7642b2dc..19a6f4a91f6 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Issues"
-= render "header_title"
= content_for :meta_tags do
- if current_user
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index b317a0c1cf4..e8aae0f47e2 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Issue"
-= render "header_title"
%h3.page-title
New Issue
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index bde80bbb54b..f3b0469b7d4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,7 +1,6 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
-- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
.clearfix.detail-page-header
.issuable-header
@@ -53,12 +52,12 @@
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line
+ = markdown escape_once(@issue.title), pipeline: :single_line, author: @issue.author
- if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
.wiki
= preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
+ = markdown(@issue.description, cache_key: [@issue, "description"], author: @issue.author)
%textarea.hidden.js-task-list-field
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
diff --git a/app/views/projects/labels/_header_title.html.haml b/app/views/projects/labels/_header_title.html.haml
deleted file mode 100644
index abe28da483b..00000000000
--- a/app/views/projects/labels/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Labels", namespace_project_labels_path(@project.namespace, @project))
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 675a805e12f..6901ba13ab7 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @label.name, "Labels"
-= render "header_title"
%h3.page-title
Edit Label
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index cc41130a9dc..2557d1a4d5b 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Labels"
-= render "header_title"
.top-area
.nav-text
@@ -18,6 +17,6 @@
- else
.nothing-here-block
- if can? current_user, :admin_label, @project
- Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
+ Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
- else
No labels created
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index e20fd7d6891..49ddf901619 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Label"
-= render "header_title"
%h3.page-title
New Label
diff --git a/app/views/projects/merge_requests/_header_title.html.haml b/app/views/projects/merge_requests/_header_title.html.haml
deleted file mode 100644
index 669a9b06bdf..00000000000
--- a/app/views/projects/merge_requests/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index e740fe8c84d..c02f94490a0 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,7 +1,7 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title.title
%span.merge-request-title-text
- = link_to_gfm merge_request.title, merge_request_path(merge_request)
+ = link_to merge_request.title, merge_request_path(merge_request)
%ul.controls
- if merge_request.merged?
%li
@@ -13,7 +13,7 @@
- if merge_request.ci_commit
%li
- = render_ci_status(merge_request.ci_commit)
+ = render_pipeline_status(merge_request.ci_commit)
- if merge_request.open? && merge_request.broken?
%li
@@ -36,16 +36,10 @@
= downvotes
- note_count = merge_request.mr_and_commit_notes.user.nonawards.count
- - if note_count > 0
- %li
- = link_to merge_request_path(merge_request) + "#notes" do
- = icon('comments')
- = note_count
- - else
- %li
- = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
- = icon('comments')
- = note_count
+ %li
+ = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do
+ = icon('comments')
+ = note_count
.merge-request-info
#{merge_request.to_reference} &middot;
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 290753d57c6..7af227129ec 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,7 +1,6 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
-- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
- if diff_view == 'parallel'
- fluid_layout true
diff --git a/app/views/projects/merge_requests/dropdowns/_branch.html.haml b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
index ba8d9a5835c..a60c445aa51 100644
--- a/app/views/projects/merge_requests/dropdowns/_branch.html.haml
+++ b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
@@ -1,5 +1,5 @@
%ul
- branches.each do |branch|
%li
- %a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
+ %a{ href: '#', class: "#{('is-active' if selected == branch)}", title: branch, data: { id: branch } }
= branch
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index b31ea5e5321..03159f123f3 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
-= render "header_title"
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index e56a44e0a79..b517e874b0f 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Merge Requests"
-= render "header_title"
= render 'projects/last_push'
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index f5bf16ef3ad..a00d3128ffe 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,5 +1,4 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
-= render "header_title"
.merge-request
= render "projects/merge_requests/show/mr_title"
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index d259968030e..2e798ce780a 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Merge Request"
-= render "header_title"
- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index a23bd8d18d0..ebf18f6ac85 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,13 +1,13 @@
.detail-page-description.content-block
%h2.title
- = markdown escape_once(@merge_request.title), pipeline: :single_line
+ = markdown escape_once(@merge_request.title), pipeline: :single_line, author: @merge_request.author
%div
- if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
+ = markdown(@merge_request.description, cache_key: [@merge_request, "description"], author: @merge_request.author)
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 55dbae598d3..13359abede7 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -26,4 +26,4 @@
%i.fa.fa-check
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
= succeed '.' do
- != markdown issues_sentence(@closes_issues), pipeline: :gfm
+ != markdown issues_sentence(@closes_issues), pipeline: :gfm, author: @merge_request.author
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 3c68d61c4b5..b79508bdc34 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -13,7 +13,7 @@
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
- ci_status: "",
+ ci_status: "#{@merge_request.ci_commit ? @merge_request.ci_commit.status : ''}",
ci_message: {
normal: "Build {{status}} for \"{{title}}\"",
preparing: "{{status}} build for \"{{title}}\""
@@ -26,4 +26,10 @@
builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
};
+ if (typeof merge_request_widget !== 'undefined') {
+ clearInterval(merge_request_widget.fetchBuildStatusInterval);
+ merge_request_widget.cancelPolling();
+ merge_request_widget.clearEventListeners();
+ }
+
merge_request_widget = new MergeRequestWidget(opts);
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 807833741af..cfdf4edac37 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -25,7 +25,10 @@
- else
= f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do
Accept Merge Request
- - if @merge_request.can_remove_source_branch?(current_user)
+ - if @merge_request.force_remove_source_branch?
+ .accept-control
+ The source branch will be removed.
+ - elsif @merge_request.can_remove_source_branch?(current_user)
.accept-control.checkbox
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index 2168294c683..b83ddcab3a4 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -2,17 +2,16 @@
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when the build succeeds.
%div
- - should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
%p
= succeed '.' do
The changes will be merged into
%span.label-branch= @merge_request.target_branch
- - if should_remove_source_branch
+ - if @merge_request.remove_source_branch?
The source branch will be removed.
- else
The source branch will not be removed.
- - remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch && @merge_request.merge_user == current_user
+ - remove_source_branch_button = !@merge_request.remove_source_branch? && @merge_request.can_remove_source_branch?(current_user) && @merge_request.merge_user == current_user
- user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
- if remove_source_branch_button || user_can_cancel_automatic_merge
.clearfix.prepend-top-10
diff --git a/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml b/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml
index a8145558ca8..57ce1959021 100644
--- a/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml
@@ -1,4 +1,6 @@
-%h4
+%h4
Ready to be merged automatically
%p
Ask someone with write access to this repository to merge this request.
+ - if @merge_request.force_remove_source_branch?
+ The source branch will be removed.
diff --git a/app/views/projects/milestones/_header_title.html.haml b/app/views/projects/milestones/_header_title.html.haml
deleted file mode 100644
index 5f4b6982a6d..00000000000
--- a/app/views/projects/milestones/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Milestones", namespace_project_milestones_path(@project.namespace, @project))
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index 43f8863163d..be682226ab6 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @milestone.title, "Milestones"
-= render "header_title"
%h3.page-title
Edit Milestone ##{@milestone.iid}
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index abe567af1dd..e6133b22f96 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,6 +1,4 @@
- page_title "Milestones"
-= render "header_title"
-
.top-area
= render 'shared/milestones_filter'
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 0d016f78313..7f372b41698 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Milestone"
-= render "header_title"
%h3.page-title
New Milestone
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 6ec84660157..19944e3e023 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,8 +1,6 @@
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
-= render "header_title"
-
.detail-page-header
.status-box{ class: status_box_class(@milestone) }
- if @milestone.closed?
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 8065663ca2a..326180ebe4e 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Network", @ref
-= render "projects/commits/header_title"
= render "projects/commits/head"
= render "head"
.project-network
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index a4c6094c69a..f9ac16b32f3 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,5 @@
- page_title 'New Project'
-- header_title "Projects", root_path
+- header_title "Projects", dashboard_projects_path
%h3.page-title
New Project
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 39be072855a..8144c1ba49e 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -1,10 +1,8 @@
-- note = notes.first # example note
--# Check if line want not changed since comment was left
-- if !defined?(line) || line == note.diff_line
- %tr.notes_holder
- %td.notes_line{ colspan: 2 }
- %td.notes_content
- %ul.notes{ data: { discussion_id: note.discussion_id } }
- = render notes
- .discussion-reply-holder
- = link_to_reply_diff(note)
+- note = notes.first
+%tr.notes_holder
+ %td.notes_line{ colspan: 2 }
+ %td.notes_content
+ %ul.notes{ data: { discussion_id: note.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes, as: :note
+ .discussion-reply-holder
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index f8aa5e2fa7d..45986b0d1e8 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -1,27 +1,27 @@
-- note1 = notes_left.present? ? notes_left.first : nil
-- note2 = notes_right.present? ? notes_right.first : nil
+- note_left = notes_left.present? ? notes_left.first : nil
+- note_right = notes_right.present? ? notes_right.first : nil
%tr.notes_holder
- - if note1
+ - if note_left
%td.notes_line.old
%td.notes_content.parallel.old
- %ul.notes{ data: { discussion_id: note1.discussion_id } }
- = render notes_left
+ %ul.notes{ data: { discussion_id: note_left.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes_left, as: :note
.discussion-reply-holder
- = link_to_reply_diff(note1, 'old')
+ = link_to_reply_discussion(note_left, 'old')
- else
%td.notes_line.old= ""
%td.notes_content.parallel.old= ""
- - if note2
+ - if note_right
%td.notes_line.new
%td.notes_content.parallel.new
- %ul.notes{ data: { discussion_id: note2.discussion_id } }
- = render notes_right
+ %ul.notes{ data: { discussion_id: note_right.discussion_id } }
+ = render partial: "projects/notes/note", collection: notes_right, as: :note
.discussion-reply-holder
- = link_to_reply_diff(note2, 'new')
+ = link_to_reply_discussion(note_right, 'new')
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index 572b00a38c7..7869d6413d8 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,13 +1,46 @@
- note = discussion_notes.first
+- expanded = !note.diff_note? || note.active?
%li.note.note-discussion.timeline-entry
.timeline-entry-inner
.timeline-icon
= link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: "avatar s40"
+ = image_tag avatar_icon(note.author), class: "avatar s40"
.timeline-content
- - if note.for_merge_request?
- - (active_notes, outdated_notes) = discussion_notes.partition(&:active?)
- = render "projects/notes/discussions/active", discussion_notes: active_notes if active_notes.length > 0
- = render "projects/notes/discussions/outdated", discussion_notes: outdated_notes if outdated_notes.length > 0
- - else
- = render "projects/notes/discussions/commit", discussion_notes: discussion_notes
+ .discussion.js-toggle-container{ class: note.discussion_id }
+ .discussion-header
+ = link_to_member(@project, note.author, avatar: false)
+
+ .inline.discussion-headline-light
+ = note.author.to_reference
+ started a discussion on
+
+ - if note.for_commit?
+ - commit = note.noteable
+ - if commit
+ commit
+ = link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace'
+ - else
+ a deleted commit
+ - else
+ - if note.active?
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
+ the diff
+ - else
+ an outdated diff
+
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago")
+
+ .discussion-actions
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
+ - if expanded
+ = icon("chevron-up")
+ - else
+ = icon("chevron-down")
+
+ Toggle discussion
+
+ .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
+ - if note.diff_note?
+ = render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes
+ - else
+ = render "projects/notes/discussions/notes", discussion_notes: discussion_notes
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index d0ac380f216..67ed38a7b22 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -6,6 +6,7 @@
= f.hidden_field :line_code
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
+ = f.hidden_field :type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index aeb7c1d5ee4..f1045bbd8c3 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,5 +1,8 @@
+- return unless note.author
+- return if note.cross_reference_not_visible_for?(current_user)
+
- note_editable = note_editable?(note)
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
+%li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
@@ -8,8 +11,8 @@
.note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
- = "#{note.author.to_reference}"
- - if !note.system
+ = note.author.to_reference
+ - unless note.system
commented
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
@@ -26,7 +29,7 @@
.note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
- = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
+ = markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
- if note_editable
= render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index 62db86fb181..ebf7e8a9cb3 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -2,14 +2,9 @@
- @discussions.each do |discussion_notes|
- note = discussion_notes.first
- if note_for_main_target?(note)
- - next if note.cross_reference_not_visible_for?(current_user)
-
- = render discussion_notes
+ = render partial: "projects/notes/note", object: note, as: :note
- else
= render 'projects/notes/discussion', discussion_notes: discussion_notes
- else
- @notes.each do |note|
- - next unless note.author
- - next if note.cross_reference_not_visible_for?(current_user)
-
- = render note
+ = render partial: "projects/notes/note", object: note, as: :note
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
deleted file mode 100644
index 0ea8862a684..00000000000
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- note = discussion_notes.first
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion"
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- on the diff
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-up
- Show/hide discussion
-
- .discussion-body.js-toggle-content
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
deleted file mode 100644
index 2a2ead58eeb..00000000000
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- note = discussion_notes.first
-- commit = note.noteable
-- commit_description = commit ? 'commit' : 'a deleted commit'
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion on #{commit_description}"
- - if commit
- = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-up
- Show/hide discussion
- .discussion-body.js-toggle-content
- - if note.for_diff_line?
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- - else
- .panel.panel-default
- .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
- %ul.notes.timeline
- = render discussion_notes
- .discussion-reply-holder
- = link_to_reply_diff(discussion_notes.first)
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
deleted file mode 100644
index d46aab000c3..00000000000
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-- diff = note.diff
-- if diff
- .diff-file
- .diff-header
- %span
- - if diff.deleted_file
- = diff.old_path
- - else
- = diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
- .diff-content.code.js-syntax-highlight
- %table
- - note.truncated_diff_lines.each do |line|
- - type = line.type
- - line_code = generate_line_code(note.file_path, line)
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- %td.old_line.diff-line-num= "..."
- %td.new_line.diff-line-num= "..."
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
- %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
- %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
-
- - if line_code == note.line_code
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
new file mode 100644
index 00000000000..6401245bf73
--- /dev/null
+++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
@@ -0,0 +1,30 @@
+- note = discussion_notes.first
+- diff = note.diff
+- return unless diff
+
+.diff-file
+ .diff-header
+ %span
+ - if diff.deleted_file
+ = diff.old_path
+ - else
+ = diff.new_path
+ - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
+ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
+ .diff-content.code.js-syntax-highlight
+ %table
+ - note.truncated_diff_lines.each do |line|
+ - type = line.type
+ - line_code = generate_line_code(note.diff_file_path, line)
+ %tr.line_holder{ id: line_code, class: "#{type}" }
+ - if type == "match"
+ %td.old_line.diff-line-num= "..."
+ %td.new_line.diff-line-num= "..."
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
+ %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
+ %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
+
+ - if line_code == note.line_code
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml
new file mode 100644
index 00000000000..e598e3c7c63
--- /dev/null
+++ b/app/views/projects/notes/discussions/_notes.html.haml
@@ -0,0 +1,7 @@
+- note = discussion_notes.first
+.panel.panel-default
+ .notes{ data: { discussion_id: note.discussion_id } }
+ %ul.notes.timeline
+ = render partial: "projects/notes/note", collection: discussion_notes, as: :note
+ .discussion-reply-holder
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
deleted file mode 100644
index 45141bcd1df..00000000000
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- note = discussion_notes.first
-.discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
- .inline.discussion-headline-light
- = "#{note.author.to_reference} started a discussion"
- on the outdated diff
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
- .discussion-actions
- = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- %i.fa.fa-chevron-down
- Show/hide discussion
- .discussion-body.js-toggle-content.hide
- = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
new file mode 100644
index 00000000000..6e757df5417
--- /dev/null
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -0,0 +1,14 @@
+%ul.nav-links
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipelines) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
+ %span.badge.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count)
+
+ - if project_nav_tab? :builds
+ = nav_link(controller: %w(builds)) do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+ %span
+ Builds
+ %span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
new file mode 100644
index 00000000000..8289aefcde7
--- /dev/null
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -0,0 +1,37 @@
+%p
+.commit-info-row
+ Pipeline
+ = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @pipeline.id), class: "monospace"
+ with
+ = pluralize @pipeline.statuses.count(:id), "build"
+ - if @pipeline.ref
+ for
+ = link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
+ - if @pipeline.duration
+ in
+ = time_interval_in_words @pipeline.duration
+
+ .pull-right
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do
+ = ci_icon_for_status(@pipeline.status)
+ = ci_label_for_status(@pipeline.status)
+
+- if @commit
+ .commit-info-row
+ %span.light Authored by
+ %strong
+ = commit_author_link(@commit, avatar: true, size: 24)
+ #{time_ago_with_tooltip(@commit.authored_date)}
+
+.commit-info-row
+ %span.light Commit
+ = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "monospace"
+ = clipboard_button(clipboard_text: @pipeline.sha)
+
+- if @commit
+ .commit-box.content-block
+ %h3.commit-title
+ = markdown escape_once(@commit.title), pipeline: :single_line
+ - if @commit.description.present?
+ %pre.commit-description
+ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
new file mode 100644
index 00000000000..453767920b5
--- /dev/null
+++ b/app/views/projects/pipelines/index.html.haml
@@ -0,0 +1,58 @@
+- page_title "Pipelines"
+= render "projects/pipelines/head"
+
+.top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_pipelines_path(@project) do
+ All
+ %span.badge.js-totalbuilds-count
+ = number_with_delimiter(@pipelines_count)
+
+ %li{class: ('active' if @scope == 'running')}
+ = link_to project_pipelines_path(@project, scope: :running) do
+ Running
+ %span.badge.js-running-count
+ = number_with_delimiter(@running_or_pending_count)
+
+ %li{class: ('active' if @scope == 'branches')}
+ = link_to project_pipelines_path(@project, scope: :branches) do
+ Branches
+
+ %li{class: ('active' if @scope == 'tags')}
+ = link_to project_pipelines_path(@project, scope: :tags) do
+ Tags
+
+ .nav-controls
+ - if can? current_user, :create_pipeline, @project
+ = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
+ = icon('plus')
+ New pipeline
+
+ - unless @repository.gitlab_ci_yml
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+
+ = link_to ci_lint_path, class: 'btn btn-default' do
+ = icon('wrench')
+ %span CI Lint
+
+%ul.content-list.pipelines
+ - stages = @pipelines.stages
+ - if @pipelines.blank?
+ %li
+ .nothing-here-block No pipelines to show
+ - else
+ .table-holder
+ %table.table.builds
+ %tbody
+ %th ID
+ %th Commit
+ - stages.each do |stage|
+ %th.stage
+ %span.has-tooltip{ title: "#{stage.titleize}" }
+ = stage.titleize.pluralize
+ %th Duration
+ %th
+ = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
+
+ = paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
new file mode 100644
index 00000000000..5f4ec2e40c8
--- /dev/null
+++ b/app/views/projects/pipelines/new.html.haml
@@ -0,0 +1,21 @@
+- page_title "New Pipeline"
+
+%h3.page-title
+ New Pipeline
+%hr
+
+= form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
+ = form_errors(@pipeline)
+ .form-group
+ = f.label :ref, 'Create for', class: 'control-label'
+ .col-sm-10
+ = f.text_field :ref, required: true, tabindex: 2, class: 'form-control'
+ .help-block Existing branch name, tag
+ .form-actions
+ = f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel'
+
+:javascript
+ var availableRefs = #{@project.repository.ref_names.to_json};
+
+ new NewBranchForm($('.js-new-pipeline-form'), availableRefs)
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
new file mode 100644
index 00000000000..2aad5602414
--- /dev/null
+++ b/app/views/projects/pipelines/show.html.haml
@@ -0,0 +1,8 @@
+- page_title "Pipeline"
+
+.prepend-top-default
+ - if @commit
+ = render "projects/pipelines/info"
+ %div.block-connector
+
+= render "projects/commit/ci_commit", ci_commit: @pipeline
diff --git a/app/views/projects/project_members/_header_title.html.haml b/app/views/projects/project_members/_header_title.html.haml
deleted file mode 100644
index a31f0a37fa2..00000000000
--- a/app/views/projects/project_members/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Members", namespace_project_project_members_path(@project.namespace, @project))
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 189906498cb..eef97107d77 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -1,5 +1,4 @@
- page_title "Import members"
-= render "header_title"
%h3.page-title
Import members from another project
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index ebcfc907ebb..15dc064e7ea 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Members"
-= render "header_title"
.project-members-page.prepend-top-default
- if can?(current_user, :admin_project_member, @project)
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index b9e9dd8aaea..565905cbe7b 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,7 +1,7 @@
%h5.prepend-top-0
Already Protected (#{@branches.size})
- if @branches.empty?
- %p.profile-settings-message.text-center
+ %p.settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
- can_admin_project = can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 0d59cec322c..835398b6f98 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @tag.name, "Tags"
-= render "projects/commits/header_title"
= render "projects/commits/head"
.row-content-block
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index 6ca919f7f80..43a6fdfd103 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
- = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
+ = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line, author: commit.author
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
new file mode 100644
index 00000000000..d62f5c8f131
--- /dev/null
+++ b/app/views/projects/runners/_form.html.haml
@@ -0,0 +1,32 @@
+= form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f|
+ = form_errors(runner)
+ .form-group
+ = label :active, "Active", class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.check_box :active
+ %span.light Paused runners don't accept new builds
+ .form-group
+ = label :run_untagged, 'Run untagged jobs', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.check_box :run_untagged
+ %span.light Indicates whether this runner can pick jobs without tags
+ .form-group
+ = label_tag :token, class: 'control-label' do
+ Token
+ .col-sm-10
+ = f.text_field :token, class: 'form-control', readonly: true
+ .form-group
+ = label_tag :description, class: 'control-label' do
+ Description
+ .col-sm-10
+ = f.text_field :description, class: 'form-control'
+ .form-group
+ = label_tag :tag_list, class: 'control-label' do
+ Tags
+ .col-sm-10
+ = f.text_field :tag_list, value: runner.tag_list.to_s, class: 'form-control'
+ .help-block You can setup jobs to only use runners with specific tags
+ .form-actions
+ = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 47ec420189d..96e2aac451f 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -5,7 +5,7 @@
- if @runners.include?(runner)
= link_to runner.short_sha, runner_path(runner)
%small
- =link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+ = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
%i.fa.fa-edit.btn
- else
= runner.short_sha
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 30cd1263a12..8ae9f0d95f7 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -8,7 +8,7 @@
Install GitLab Runner software.
Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
%li
- Specify following URL during runner setup:
+ Specify the following URL during runner setup:
%code #{ci_root_url(only_path: false)}
%li
Use the following registration token during setup:
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
index eba03028af8..95706888655 100644
--- a/app/views/projects/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -1,29 +1,6 @@
- page_title "Edit", "#{@runner.description} ##{@runner.id}", "Runners"
%h4 Runner ##{@runner.id}
+
%hr
-= form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f|
- .form-group
- = label :active, "Active", class: 'control-label'
- .col-sm-10
- .checkbox
- = f.check_box :active
- %span.light Paused runners don't accept new builds
- .form-group
- = label_tag :token, class: 'control-label' do
- Token
- .col-sm-10
- = f.text_field :token, class: 'form-control', readonly: true
- .form-group
- = label_tag :description, class: 'control-label' do
- Description
- .col-sm-10
- = f.text_field :description, class: 'form-control'
- .form-group
- = label_tag :tag_list, class: 'control-label' do
- Tags
- .col-sm-10
- = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
- .help-block You can setup jobs to only use runners with specific tags
- .form-actions
- = f.submit 'Save changes', class: 'btn btn-save'
+ = render 'form', runner: @runner, runner_form_url: runner_path(@runner)
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index 5bf4c09ca25..f24e1b9144e 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -17,50 +17,39 @@
%th Property Name
%th Value
%tr
- %td
- Tags
+ %td Active
+ %td= @runner.active? ? 'Yes' : 'No'
+ %tr
+ %td Can run untagged jobs
+ %td= @runner.run_untagged? ? 'Yes' : 'No'
+ %tr
+ %td Tags
%td
- @runner.tag_list.each do |tag|
%span.label.label-primary
= tag
%tr
- %td
- Name
- %td
- = @runner.name
+ %td Name
+ %td= @runner.name
%tr
- %td
- Version
- %td
- = @runner.version
+ %td Version
+ %td= @runner.version
%tr
- %td
- Revision
- %td
- = @runner.revision
+ %td Revision
+ %td= @runner.revision
%tr
- %td
- Platform
- %td
- = @runner.platform
+ %td Platform
+ %td= @runner.platform
%tr
- %td
- Architecture
- %td
- = @runner.architecture
+ %td Architecture
+ %td= @runner.architecture
%tr
- %td
- Description
- %td
- = @runner.description
+ %td Description
+ %td= @runner.description
%tr
- %td
- Last contact
+ %td Last contact
%td
- if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago
- else
Never
-
-
-
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 1b70880043a..1f13ea28b4e 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -1,18 +1,16 @@
-%h3.page-title
- = @service.title
- = boolean_to_icon @service.activated?
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = @service.title
+ = boolean_to_icon @service.activated?
-%p= @service.description
-
-%hr
-
-= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
- = render 'shared/service_settings', form: form
-
- .form-actions
- = form.submit 'Save changes', class: 'btn btn-save'
- &nbsp;
- - if @service.valid? && @service.activated?
- - disabled = @service.can_test? ? '':'disabled'
- = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
- = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
+ %p= @service.description
+ .col-lg-9
+ = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
+ = render 'shared/service_settings', form: form
+ = form.submit 'Save changes', class: 'btn btn-save'
+ &nbsp;
+ - if @service.valid? && @service.activated?
+ - disabled = @service.can_test? ? '':'disabled'
+ = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+ = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index c1356f6db02..4a33a5bc6f6 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -1,24 +1,32 @@
- page_title "Services"
-%h3.page-title Project services
-%p.light Project services allow you to integrate GitLab with other applications
-.table-holder
- %table.table
- %thead
- %tr
- %th
- %th Service
- %th Description
- %th Last edit
- - @services.sort_by(&:title).each do |service|
- %tr
- %td
- = boolean_to_icon service.activated?
- %td
- = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_in_words service.updated_at
- ago
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Project services
+ %p Project services allow you to integrate GitLab with other applications
+ .col-lg-9
+ %table.table
+ %colgroup
+ %col
+ %col
+ %col.hidden-xs
+ %col{ width: "120" }
+ %thead
+ %tr
+ %th
+ %th Service
+ %th.hidden-xs Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ %strong= service.title
+ %td.hidden-xs
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index 9b3d3f069d9..11310d5e1e1 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -6,7 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id namespace_project_url(@project.namespace, @project)
xml.updated @events[0].updated_at.xmlschema if @events[0]
- @events.each do |event|
- event_to_atom(xml, event)
- end
+ xml << render(@events) if @events.any?
end
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 74feb9e3282..a19c7c406a0 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -13,50 +13,50 @@
= render "home_panel"
.project-stats.row-content-block.second-block
- %ul.nav
- %li
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
- = pluralize(number_with_delimiter(@project.commit_count), 'commit')
- %li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
- %li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
-
- %li
- = link_to project_files_path(@project) do
- = repository_size
-
- - if default_project_view != 'readme' && @repository.readme
+ .container-fluid.container-limited
+ %ul.nav
%li
- = link_to 'Readme', readme_path(@project)
-
- - if @repository.changelog
+ = link_to project_files_path(@project) do
+ Files (#{repository_size})
%li
- = link_to 'Changelog', changelog_path(@project)
-
- - if @repository.license_blob
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
- = link_to license_short_name(@project), license_path(@project)
-
- - if @repository.contribution_guide
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ #{'Branch'.pluralize(@repository.branch_names.count)} (#{number_with_delimiter(@repository.branch_names.count)})
%li
- = link_to 'Contribution guide', contribution_guide_path(@project)
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ #{'Tag'.pluralize(@repository.tag_names.count)} (#{number_with_delimiter(@repository.tag_names.count)})
+
+ - if default_project_view != 'readme' && @repository.readme
+ %li
+ = link_to 'Readme', readme_path(@project)
+
+ - if @repository.changelog
+ %li
+ = link_to 'Changelog', changelog_path(@project)
+
+ - if @repository.license_blob
+ %li
+ = link_to license_short_name(@project), license_path(@project)
+
+ - if @repository.contribution_guide
+ %li
+ = link_to 'Contribution guide', contribution_guide_path(@project)
- - if current_user && can_push_branch?(@project, @project.default_branch)
- - unless @repository.changelog
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
- Add Changelog
- - unless @repository.license_blob
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'LICENSE') do
- Add License
- - unless @repository.contribution_guide
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
- Add Contribution guide
+ - if current_user && can_push_branch?(@project, @project.default_branch)
+ - unless @repository.changelog
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
+ Add Changelog
+ - unless @repository.license_blob
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
+ Add License
+ - unless @repository.contribution_guide
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
+ Add Contribution guide
- if @repository.commit
.content-block.second-block.white
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 4a515469422..bf57beb9d07 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,11 +1,27 @@
-= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
- = icon('plus')
- New Snippet
-- if can?(current_user, :admin_project_snippet, @snippet)
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
- = icon('trash-o')
- Delete
-- if can?(current_user, :update_project_snippet, @snippet)
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
- = icon('pencil-square-o')
- Edit
+.hidden-xs
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+ - if can?(current_user, :update_project_snippet, @snippet)
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
+ Edit
+ - if can?(current_user, :update_project_snippet, @snippet)
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
+ Delete
+.visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ Edit
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/projects/snippets/_header_title.html.haml b/app/views/projects/snippets/_header_title.html.haml
deleted file mode 100644
index 04f0bbe9853..00000000000
--- a/app/views/projects/snippets/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, "Snippets", namespace_project_snippets_path(@project.namespace, @project))
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index dc3ea1fcf12..216f70f5605 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @snippet.title, "Snippets"
-= render "header_title"
%h3.page-title
Edit Snippet
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 103ff447464..96fee3b17b2 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Snippets"
-= render "header_title"
.row-content-block.top-block
.pull-right
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index e57237991b4..772a594269c 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Snippets"
-= render "header_title"
%h3.page-title
New Snippet
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 7c599563ce4..bae4d8f349f 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,18 +1,15 @@
- page_title @snippet.title, "Snippets"
-= render "header_title"
.snippet-holder
= render 'shared/snippets/header'
- %article.file-holder
- .file-title
+ %article.file-holder.file-holder-no-border.snippet-file-content
+ .file-title.file-title-clear
= blob_icon 0, @snippet.file_name
- %strong
- = @snippet.file_name
+ = @snippet.file_name
.file-actions.hidden-xs
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
-
= render 'shared/snippets/blob'
%div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
index ffeacb5a004..e4a78fadbeb 100644
--- a/app/views/projects/tags/destroy.js.haml
+++ b/app/views/projects/tags/destroy.js.haml
@@ -1,3 +1,2 @@
-$('.js-totaltags-count').html("#{@repository.tags.size}");
- if @repository.tags.empty?
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index dc6ece30dd2..8f381663e6e 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Tags"
-= render "projects/commits/header_title"
= render "projects/commits/head"
.row-content-block
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index b40a6e5cb2d..3a097750d6e 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -1,5 +1,4 @@
- page_title "New Tag"
-= render "projects/commits/header_title"
- if @error
.alert.alert-danger
@@ -23,7 +22,7 @@
.form-group
= label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+ = text_area_tag :message, nil, required: false, tabindex: 3, class: 'form-control', rows: 5
.help-block Optionally, enter a message to create an annotated tag.
%hr
.form-group
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 9c916fd02de..b7d7d5c5382 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,5 +1,4 @@
- page_title @tag.name, "Tags"
-= render "projects/commits/header_title"
= render "projects/commits/head"
.row-content-block
@@ -19,15 +18,13 @@
%i.fa.fa-trash-o
.title
%span.item-title= @tag.name
- - if @tag.message.present?
- %span.light
- &nbsp;
- = strip_gpg_signature(@tag.message)
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
-
+ - if @tag.message.present?
+ %pre.body
+ = strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default
- if @release.description.present?
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 91fb2a44594..59f60c4687c 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,9 +1,9 @@
- page_title @path.presence || "Files", @ref
-- header_title project_title(@project, "Files", project_files_path(@project))
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push'
+= render "projects/commits/head"
.tree-controls
= render 'projects/find_file_link'
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index f91885b216d..7f3de47d7df 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -5,7 +5,7 @@
%h4.prepend-top-0
= page_title
%p
- Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+ Triggers can force a specific branch or tag to rebuild with an API call.
.col-lg-9
%h5.prepend-top-0
Your triggers
@@ -18,8 +18,8 @@
%th
= render partial: 'trigger', collection: @triggers, as: :trigger
- else
- %p.profile-settings-message.text-center.append-bottom-default
- There are no triggers to use, add one by the button below.
+ %p.settings-message.text-center.append-bottom-default
+ No triggers have been created yet. Add one using the button below.
= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
= f.submit "Add Trigger", class: 'btn btn-success'
@@ -28,8 +28,7 @@
Use CURL
%p.light
- Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
-
+ Copy the token above, set your branch or tag name, and that reference will be rebuilt.
%pre
:plain
@@ -41,10 +40,10 @@
Use .gitlab-ci.yml
%p.light
- Copy the snippet to
- %i .gitlab-ci.yml
- of dependent project.
- At the end of your build it will trigger this project to rebuilt.
+ In the
+ %code .gitlab-ci.yml
+ of the dependent project, include the following snippet.
+ The project will rebuild at the end of the build.
%pre
:plain
@@ -57,9 +56,8 @@
%p.light
Add
- %strong variables[VARIABLE]=VALUE
- to API request.
- The value of variable could then be used to distinguish triggered build from normal one.
+ %code variables[VARIABLE]=VALUE
+ to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
%pre.append-bottom-0
:plain
diff --git a/app/views/projects/variables/_content.html.haml b/app/views/projects/variables/_content.html.haml
new file mode 100644
index 00000000000..0249e0c1bf1
--- /dev/null
+++ b/app/views/projects/variables/_content.html.haml
@@ -0,0 +1,8 @@
+%h4.prepend-top-0
+ Secret Variables
+%p
+ These variables will be set to environment by the runner.
+%p
+ So you can use them for passwords, secret keys or whatever you want.
+%p
+ The value of the variable can be visible in build log if explicitly asked to do so.
diff --git a/app/views/projects/variables/_form.html.haml b/app/views/projects/variables/_form.html.haml
new file mode 100644
index 00000000000..a5bae83e0ce
--- /dev/null
+++ b/app/views/projects/variables/_form.html.haml
@@ -0,0 +1,10 @@
+= form_for [@project.namespace.becomes(Namespace), @project, @variable] do |f|
+ = form_errors(@variable)
+
+ .form-group
+ = f.label :key, "Key", class: "label-light"
+ = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
+ .form-group
+ = f.label :value, "Value", class: "label-light"
+ = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
+ = f.submit btn_text, class: "btn btn-save"
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml
new file mode 100644
index 00000000000..6c43f822db4
--- /dev/null
+++ b/app/views/projects/variables/_table.html.haml
@@ -0,0 +1,25 @@
+.table-responsive.variables-table
+ %table.table
+ %colgroup
+ %col
+ %col
+ %col{ width: 100 }
+ %thead
+ %th Key
+ %th Value
+ %th
+ %tbody
+ - @project.variables.each do |variable|
+ - if variable.id?
+ %tr
+ %td= variable.key
+ %td= variable.value
+ %td
+ = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
+ %span.sr-only
+ Update
+ = icon("pencil")
+ = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
+ %span.sr-only
+ Remove
+ = icon("trash")
diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml
new file mode 100644
index 00000000000..09bb54600af
--- /dev/null
+++ b/app/views/projects/variables/index.html.haml
@@ -0,0 +1,17 @@
+- page_title "Variables"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ = render "content"
+ .col-lg-9
+ %h5.prepend-top-0
+ Add a variable
+ = render "form", btn_text: "Add new variable"
+ %hr
+ %h5.prepend-top-0
+ Your variables (#{@project.variables.size})
+ - if @project.variables.empty?
+ %p.settings-message.text-center.append-bottom-0
+ No variables found, add one with the form above.
+ - else
+ = render "table"
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index ca284b84d39..297a53ca98c 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -1,36 +1,9 @@
- page_title "Variables"
-%h3.page-title
- Secret Variables
-%p.light
- These variables will be set to environment by the runner.
- %br
- So you can use them for passwords, secret keys or whatever you want.
- %br
- The value of the variable can be visible in build log if explicitly asked to do so.
-
-%hr
-
-
-= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- = form_errors(@project)
-
- = f.fields_for :variables do |variable_form|
- .form-group
- = variable_form.label :key, 'Key', class: 'control-label'
- .col-sm-10
- = variable_form.text_field :key, class: 'form-control', placeholder: "PROJECT_VARIABLE"
-
- .form-group
- = variable_form.label :value, 'Value', class: 'control-label'
- .col-sm-10
- = variable_form.text_area :value, class: 'form-control', rows: 2, placeholder: ""
-
- = variable_form.link_to_remove "Remove this variable", class: 'btn btn-danger pull-right prepend-top-10'
- %hr
- %p
- .clearfix
- = f.link_to_add "Add a variable", :variables, class: 'btn btn-success pull-right'
-
- .form-actions
- = f.submit 'Save changes', class: 'btn btn-save', return_to: request.original_url
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ = render "content"
+ .col-lg-9
+ %h5.prepend-top-0
+ Update variable
+ = render "form", btn_text: "Save variable"
diff --git a/app/views/projects/wikis/_header_title.html.haml b/app/views/projects/wikis/_header_title.html.haml
deleted file mode 100644
index 408adc36ca6..00000000000
--- a/app/views/projects/wikis/_header_title.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-- header_title project_title(@project, 'Wiki', get_project_wiki_path(@project))
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 4dd818c7f67..aaa15dd3bbe 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Edit", @page.title.capitalize, "Wiki"
-= render "header_title"
= render 'nav'
.top-area
diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml
index c7e490c3cd1..7dfa405d063 100644
--- a/app/views/projects/wikis/empty.html.haml
+++ b/app/views/projects/wikis/empty.html.haml
@@ -1,5 +1,4 @@
- page_title "Wiki"
-= render "header_title"
%h3.page-title Empty page
%hr
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index ba3f2cadc48..ccceab6155e 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,5 +1,4 @@
- page_title "Git Access", "Wiki"
-= render "header_title"
= render 'nav'
.row-content-block
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index dcaddae2b04..45460ed9f41 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,5 +1,4 @@
- page_title "History", @page.title.capitalize, "Wiki"
-= render "header_title"
= render 'nav'
.top-area
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 92b494a513c..2f6162fa3c5 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,5 +1,4 @@
- page_title "Pages", "Wiki"
-= render "header_title"
= render 'nav'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 067fb7f8f54..1cb48a1e85d 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,5 +1,4 @@
- page_title @page.title.capitalize, "Wiki"
-= render "header_title"
= render 'nav'
.top-area
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 640890fbe92..8f68d6d1b87 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -7,7 +7,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project }))
+ = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project, author: issue.author }))
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 333f6533213..6331c2bd6b0 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -6,7 +6,7 @@
- if merge_request.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project }))
+ = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project, author: merge_request.author }))
%span.light
#{merge_request.project.name_with_namespace}
.pull-right
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index d9400b1d9fa..8163aff43b6 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -19,4 +19,4 @@
.note-search-result
.term
= preserve do
- = search_md_sanitize(markdown(note.note, {no_header_anchors: true}))
+ = search_md_sanitize(markdown(note.note, {no_header_anchors: true, author: note.author}))
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 974751d9970..84b3f44c0ad 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -5,7 +5,7 @@
%a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
%span
= default_clone_protocol.upcase
- = icon('angle-down')
+ = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
%li
= ssh_clone_button(project)
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index c38d9313dba..30055002213 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,5 +1,7 @@
-%ul.nav-links.event-filter
+%ul.nav-links.event-filter.scrolling-tabs
+ .fade-left
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
+ .fade-right
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index d327bd0a96f..1e0f075b303 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -6,7 +6,7 @@
- else
= sort_title_recently_created
%b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
+ %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
= link_to page_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
diff --git a/app/views/shared/groups/_list.html.haml b/app/views/shared/groups/_list.html.haml
index 1aa7ed1f2eb..427595c47a5 100644
--- a/app/views/shared/groups/_list.html.haml
+++ b/app/views/shared/groups/_list.html.haml
@@ -3,4 +3,4 @@
- groups.each_with_index do |group, i|
= render "shared/groups/group", group: group
- else
- %h3 No groups found
+ .nothing-here-block No groups found
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 9474462cbd1..cedff4af2e0 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,6 +1,8 @@
.issues-filters
.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
+ = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
+ - if params[:issue_search].present?
+ = hidden_field_tag :issue_search, params[:issue_search]
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
= check_box_tag "check_all_issues", nil, false,
@@ -10,7 +12,7 @@
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
- placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
+ placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id].present?
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 5c52cc6d1da..b430251dbf6 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -44,45 +44,53 @@
This issue is confidential and should only be visible to team members
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+ - has_due_date = issuable.has_attribute?(:due_date)
%hr
- .form-group
- .issue-assignee
- = f.label :assignee_id, "Assignee", class: 'control-label'
- .col-sm-10
- .issuable-form-select-holder
- = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
- selected: issuable.assignee_id, project: @target_project || @project,
- first_user: true, current_user: true, include_blank: true)
- &nbsp;
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id, "Milestone", class: 'control-label'
- .col-sm-10
- - if milestone_options(issuable).present?
+ .row
+ %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
+ .form-group.issue-assignee
+ = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+ .col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = f.select(:milestone_id, milestone_options(issuable),
- { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- - else
- .prepend-top-10
- %span.light No open milestones available.
- &nbsp;
- - if can? current_user, :admin_milestone, issuable.project
- = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
- .form-group
- - has_labels = issuable.project.labels.any?
- = f.label :label_ids, "Labels", class: 'control-label'
- .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
- - if has_labels
- .issuable-form-select-holder
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- - else
- %span.light No labels yet.
- &nbsp;
- - if can? current_user, :admin_label, issuable.project
- = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+ = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
+ placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
+ selected: issuable.assignee_id, project: @target_project || @project,
+ first_user: true, current_user: true, include_blank: true)
+ %div
+ = link_to 'Assign to me', '#', class: 'assign-to-me-link prepend-top-5 inline'
+ .form-group.issue-milestone
+ = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
+ .col-sm-10{ class: ("col-lg-8" if has_due_date) }
+ - if milestone_options(issuable).present?
+ .issuable-form-select-holder
+ = f.select(:milestone_id, milestone_options(issuable),
+ { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
+ - else
+ .prepend-top-10
+ %span.light No open milestones available.
+ - if can? current_user, :admin_milestone, issuable.project
+ %div
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline"
+ .form-group
+ - has_labels = issuable.project.labels.any?
+ = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
+ .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
+ - if has_labels
+ .issuable-form-select-holder
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
+ - else
+ %span.light No labels yet.
+ - if can? current_user, :admin_label, issuable.project
+ %div
+ = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline"
+ - if has_due_date
+ .col-lg-6
+ .form-group
+ = f.label :due_date, "Due date", class: "control-label"
+ = f.hidden_field :due_date, id: "issuable-due-date"
+ .col-sm-10
+ .datepicker
- if issuable.can_move?(current_user)
%hr
@@ -90,9 +98,7 @@
= label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10
.issuable-form-select-holder
- - projects = project_options(issuable, current_user, ability: :admin_issue)
- = select_tag(:move_to_project_id, projects, include_blank: true,
- class: 'select2', data: { placeholder: 'Select project' })
+ = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) }
&nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
@@ -114,6 +120,13 @@
- if @merge_request.new_record?
&nbsp;
= link_to 'Change branches', mr_change_branches_path(@merge_request)
+ - if @merge_request.can_remove_source_branch?(current_user)
+ .form-group
+ .col-sm-10.col-sm-offset-2
+ .checkbox
+ = label_tag 'merge_request[force_remove_source_branch]' do
+ = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch?
+ Remove source branch when merge request is accepted.
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
.row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
diff --git a/app/views/shared/issuable/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index afad48499b7..186963b32b8 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,8 +1,2 @@
= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
- = hidden_field_tag :state, params['state']
- = hidden_field_tag :scope, params['scope']
- = hidden_field_tag :assignee_id, params['assignee_id']
- = hidden_field_tag :author_id, params['author_id']
- = hidden_field_tag :milestone_id, params['milestone_id']
- = hidden_field_tag :label_id, params['label_id']
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ed1b8a8da2a..d6552ae7f18 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -87,10 +87,16 @@
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed
- - if issuable.due_date
- = issuable.due_date.to_s(:medium)
- - else
- .light None
+ %span.value-content
+ - if issuable.due_date
+ = issuable.due_date.to_s(:medium)
+ - else
+ None
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ %span.light.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) }
+ \-
+ %a.js-remove-due-date{ href: "#", role: "button" }
+ remove due date
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.selectbox.hide-collapsed
= f.hidden_field :due_date, value: issuable.due_date
@@ -108,20 +114,20 @@
.sidebar-collapsed-icon
= icon('tags')
%span
- = issuable.labels.count
+ = issuable.labels_array.size
.title.hide-collapsed
Labels
= icon('spinner spin', class: 'block-loading')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
- .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
- - if issuable.labels.any?
- - issuable.labels.each do |label|
+ .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
+ - if issuable.labels_array.any?
+ - issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
.selectbox.hide-collapsed
- - issuable.labels.each do |label|
+ - issuable.labels_array.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml
index 67ae85ac276..549d2e2f61e 100644
--- a/app/views/shared/milestones/_participants_tab.html.haml
+++ b/app/views/shared/milestones/_participants_tab.html.haml
@@ -3,6 +3,6 @@
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
+ %strong= truncate(user.name, length: 40)
%br
%small.cgray= user.username
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index ab8b022411d..b8b66d08db8 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,12 +12,9 @@
%li.project-row{ class: css_class }
= cache(cache_key) do
.controls
- - if project.main_language
- %span
- = project.main_language
- if project.commit.try(:status)
%span
- = render_ci_status(project.commit)
+ = render_commit_status(project.commit)
- if forks
%span
= icon('code-fork')
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index e65b1814872..af753496260 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,25 +1,24 @@
-.detail-page-header
- .snippet-box.has-tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+.detail-page-header.clearfix
+ .snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
+ %span.sr-only
+ = visibility_level_label(@snippet.visibility_level)
= visibility_level_icon(@snippet.visibility_level, fw: false)
- = visibility_level_label(@snippet.visibility_level)
- %span.identifier
- Snippet ##{@snippet.id}
+ %strong.item-title
+ Snippet #{@snippet.to_reference}
%span.creator
- &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
- &middot;
+ created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")}
= time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
- if @snippet.updated_at != @snippet.created_at
%span
- &middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
- .pull-right
+ .snippet-actions
- if @snippet.project_id?
= render "projects/snippets/actions"
- else
= render "snippets/actions"
-.detail-page-description.row-content-block.second-block
- %h2.title
- = markdown escape_once(@snippet.title), pipeline: :single_line
+.content-block.second-block
+ %h2.snippet-title.prepend-top-0.append-bottom-0
+ = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 1979ae6d5bc..a7769654b61 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -1,11 +1,27 @@
-= link_to new_snippet_path, class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
- = icon('plus')
- New Snippet
-- if can?(current_user, :update_personal_snippet, @snippet)
- = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
- = icon('pencil-square-o')
- Edit
-- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
- = icon('trash-o')
- Delete
+.hidden-xs
+ = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+ - if can?(current_user, :update_personal_snippet, @snippet)
+ = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+ Edit
+ - if can?(current_user, :admin_personal_snippet, @snippet)
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
+ Delete
+.visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
+ %li
+ = link_to new_snippet_path, title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_personal_snippet, @snippet)
+ %li
+ = link_to edit_snippet_path(@snippet) do
+ Edit
+ - if can?(current_user, :admin_personal_snippet, @snippet)
+ %li
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index a2b36568770..ed3992650d4 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,11 +3,10 @@
.snippet-holder
= render 'shared/snippets/header'
- %article.file-holder
- .file-title
+ %article.file-holder.file-holder-no-border.snippet-file-content
+ .file-title.file-title-clear
= blob_icon 0, @snippet.file_name
- %strong
- = @snippet.file_name
+ = @snippet.file_name
.file-actions.hidden-xs
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 1de71f37d1a..77f2ddefb1e 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -1,10 +1,9 @@
-#cal-heatmap.calendar
- :javascript
- new Calendar(
- #{@timestamps.to_json},
- #{@starting_year},
- #{@starting_month},
- '#{user_calendar_activities_path}'
- );
-
-.calendar-hint Summary of issues, merge requests, and push events
+.clearfix.calendar
+ .js-contrib-calendar
+ .calendar-hint
+ Summary of issues, merge requests, and push events
+:javascript
+ new Calendar(
+ #{@timestamps.to_json},
+ '#{user_calendar_activities_path}'
+ );
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index 027a93a75fc..630d97e339d 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -1,23 +1,27 @@
%h4.prepend-top-20
- %span.light Contributions for
+ Contributions for
%strong #{@calendar_date.to_s(:short)}
-%ul.bordered-list
- - @events.sort_by(&:created_at).each do |event|
- %li
- %span.light
- %i.fa.fa-clock-o
- = event.created_at.to_s(:time)
- - if event.push?
- #{event.action_name} #{event.ref_type} #{event.ref_name}
- - else
- = event_action_name(event)
- - if event.target
- %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
-
- at
- %strong
- - if event.project
- = link_to_project event.project
+- if @events.any?
+ %ul.bordered-list
+ - @events.sort_by(&:created_at).each do |event|
+ %li
+ %span.light
+ %i.fa.fa-clock-o
+ = event.created_at.to_s(:time)
+ - if event.push?
+ #{event.action_name} #{event.ref_type} #{event.ref_name}
- else
- = event.project_name
+ = event_action_name(event)
+ - if event.target
+ %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+
+ at
+ %strong
+ - if event.project
+ = link_to_project event.project
+ - else
+ = event.project_name
+- else
+ %p
+ No contributions found for #{@calendar_date.to_s(:short)}
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index e9e466c6350..6c85e5f9fbd 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -6,7 +6,5 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.id user_url(@user)
xml.updated @events[0].updated_at.xmlschema if @events[0]
- @events.each do |event|
- event_to_atom(xml, event)
- end
+ xml << render(@events) if @events.any?
end
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 3c0b89c6741..8268380dafc 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,5 +1,6 @@
- page_title @user.name
- page_description @user.bio
+- page_specific_javascripts asset_path("users/application.js")
- header_title @user.name, user_path(@user)
- @no_container = true
@@ -81,15 +82,17 @@
%li.projects-tab
= link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects
+ %li.snippets-tab
+ = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do
+ Snippets
%div{ class: container_class }
.tab-content
#activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs
- %div{ class: container_class }
- .user-calendar{data: {href: user_calendar_path}}
- %h4.center.light
- %i.fa.fa-spinner.fa-spin
+ .user-calendar{data: {href: user_calendar_path}}
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
.user-calendar-activities
.content_list{ data: {href: user_path} }
@@ -104,6 +107,9 @@
#projects.tab-pane
- # This tab is always loaded via AJAX
+ #snippets.tab-pane
+ - # This tab is always loaded via AJAX
+
.loading-status
= spinner
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index c4d8595d45d..971f969e25e 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -1,6 +1,9 @@
class EmailsOnPushWorker
include Sidekiq::Worker
+ sidekiq_options queue: :mailers
+ attr_reader :email, :skip_premailer
+
def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys!
options.reverse_merge!(
@@ -25,15 +28,18 @@ class EmailsOnPushWorker
:push
end
+ diff_refs = nil
compare = nil
reverse_compare = false
if action == :push
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
+ diff_refs = [project.merge_base_commit(before_sha, after_sha), project.commit(after_sha)]
return false if compare.same
if compare.commits.empty?
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
+ diff_refs = [project.merge_base_commit(after_sha, before_sha), project.commit(before_sha)]
reverse_compare = true
@@ -41,26 +47,42 @@ class EmailsOnPushWorker
end
end
- recipients.split(" ").each do |recipient|
+ recipients.split.each do |recipient|
begin
- Notify.repository_push_email(
- project_id,
+ send_email(
recipient,
- author_id: author_id,
- ref: ref,
- action: action,
- compare: compare,
- reverse_compare: reverse_compare,
- send_from_committer_email: send_from_committer_email,
- disable_diffs: disable_diffs
- ).deliver_now
+ project_id,
+ author_id: author_id,
+ ref: ref,
+ action: action,
+ compare: compare,
+ reverse_compare: reverse_compare,
+ diff_refs: diff_refs,
+ send_from_committer_email: send_from_committer_email,
+ disable_diffs: disable_diffs
+ )
+
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
end
end
ensure
+ @email = nil
compare = nil
GC.start
end
+
+ private
+
+ def send_email(recipient, project_id, options)
+ # Generating the body of this email can be expensive, so only do it once
+ @skip_premailer ||= email.present?
+ @email ||= Notify.repository_push_email(project_id, options)
+
+ email.to = recipient
+ email.add_message_id
+ email.header[:skip_premailer] = true if skip_premailer
+ email.deliver_now
+ end
end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index f9e32337983..d947f105516 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -15,8 +15,7 @@ class RepositoryForkWorker
result = gitlab_shell.fork_repository(source_path, target_path)
unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
- project.update(import_error: "The project could not be forked.")
- project.import_fail
+ project.mark_import_as_failed('The project could not be forked.')
return
end
@@ -24,8 +23,7 @@ class RepositoryForkWorker
unless project.valid_repo?
logger.error("Project #{project_id} had an invalid repository after fork")
- project.update(import_error: "The forked repository is invalid.")
- project.import_fail
+ project.mark_import_as_failed('The forked repository is invalid.')
return
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 2937493c614..7d819fe78f8 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -13,8 +13,7 @@ class RepositoryImportWorker
result = Projects::ImportService.new(project, current_user).execute
if result[:status] == :error
- project.update(import_error: result[:message])
- project.import_fail
+ project.mark_import_as_failed(result[:message])
return
end
diff --git a/config/application.rb b/config/application.rb
index b602e2b6168..49d4d3ba555 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,23 +1,32 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
-require 'devise'
-I18n.config.enforce_available_locales = false
+
Bundler.require(:default, Rails.env)
-require_relative '../lib/gitlab/redis'
module Gitlab
class Application < Rails::Application
+ require_dependency Rails.root.join('lib/gitlab/redis')
+
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
- # Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths.push(*%W(#{config.root}/lib
- #{config.root}/app/models/hooks
- #{config.root}/app/models/concerns
- #{config.root}/app/models/project_services
- #{config.root}/app/models/members))
+ # Sidekiq uses eager loading, but directories not in the standard Rails
+ # directories must be added to the eager load paths:
+ # https://github.com/mperham/sidekiq/wiki/FAQ#why-doesnt-sidekiq-autoload-my-rails-application-code
+ # Also, there is no need to add `lib` to autoload_paths since autoloading is
+ # configured to check for eager loaded paths:
+ # https://github.com/rails/rails/blob/v4.2.6/railties/lib/rails/engine.rb#L687
+ # This is a nice reference article on autoloading/eager loading:
+ # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
+ config.eager_load_paths.push(*%W(#{config.root}/lib
+ #{config.root}/app/models/ci
+ #{config.root}/app/models/hooks
+ #{config.root}/app/models/members
+ #{config.root}/app/models/project_services))
+
+ config.generators.templates.push("#{config.root}/generator_templates")
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
@@ -32,7 +41,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- #
+ #
# Parameters filtered:
# - Password (:password, :password_confirmation)
# - Private tokens (:private_token)
@@ -71,6 +80,9 @@ module Gitlab
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
config.assets.precompile << "notify.css"
+ config.assets.precompile << "mailers/*.css"
+ config.assets.precompile << "graphs/application.js"
+ config.assets.precompile << "users/application.js"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/boot.rb b/config/boot.rb
index 4489e58688c..f2830ae3166 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -3,4 +3,4 @@ require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
+require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 4f39016bfa4..8cca0039b4a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -39,6 +39,7 @@ Rails.application.configure do
config.action_mailer.delivery_method = :letter_opener_web
# Don't make a mess when bootstrapping a development environment
config.action_mailer.perform_deliveries = (ENV['BOOTSTRAP'] != '1')
+ config.action_mailer.preview_path = 'spec/mailers/previews'
config.eager_load = false
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index a703c0934f7..fb25d3a8b14 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -20,7 +20,7 @@ Rails.application.configure do
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e682bcb976d..0510e7df597 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -98,6 +98,7 @@ production: &base
wiki: true
snippets: false
builds: true
+ container_registry: true
## Webhook settings
# Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10)
@@ -175,6 +176,15 @@ production: &base
repository_archive_cache_worker:
cron: "0 * * * *"
+ registry:
+ # enabled: true
+ # host: registry.example.com
+ # port: 5005
+ # api_url: http://localhost:5000/ # internal address to the registry, will be used by GitLab to directly communicate with API
+ # key_path: config/registry.key
+ # path: shared/registry
+ # issuer: gitlab-issuer
+
#
# 2. GitLab CI settings
# ==========================
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8db2c05fe45..436751b9d16 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1,4 +1,4 @@
-require 'gitlab' # Load lib/gitlab.rb as soon as possible
+require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible
class Settings < Settingslogic
source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" }
@@ -52,7 +52,7 @@ class Settings < Settingslogic
# check that values in `current` (string or integer) is a contant in `modul`.
def verify_constant_array(modul, current, default)
values = default || []
- if !current.nil?
+ unless current.nil?
values = []
current.each do |constant|
values.push(verify_constant(modul, constant, nil))
@@ -126,7 +126,7 @@ end
Settings['omniauth'] ||= Settingslogic.new({})
-Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
+Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
Settings.omniauth['external_providers'] = [] if Settings.omniauth['external_providers'].nil?
@@ -134,7 +134,7 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block
Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil?
-Settings.omniauth['providers'] ||= []
+Settings.omniauth['providers'] ||= []
Settings.omniauth['cas3'] ||= Settingslogic.new({})
Settings.omniauth.cas3['session_duration'] ||= 8.hours
Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
@@ -168,7 +168,7 @@ end
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
-Settings['issues_tracker'] ||= {}
+Settings['issues_tracker'] ||= {}
#
# GitLab
@@ -183,7 +183,7 @@ Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
-Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
@@ -196,7 +196,7 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
-Settings.gitlab['time_zone'] ||= nil
+Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
@@ -206,12 +206,13 @@ Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
Settings.gitlab['session_expire_delay'] ||= 10080
-Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
-Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
-Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
-Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
-Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
-Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
+Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
+Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
+Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
+Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
+Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
+Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
+Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
Settings.gitlab['restricted_signup_domains'] ||= []
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
@@ -225,8 +226,8 @@ Settings['gitlab_ci'] ||= Settingslogic.new({})
Settings.gitlab_ci['shared_runners_enabled'] = true if Settings.gitlab_ci['shared_runners_enabled'].nil?
Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil?
Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil?
-Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root)
+Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
#
# Reply by email
@@ -240,7 +241,20 @@ Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled']
Settings['artifacts'] ||= Settingslogic.new({})
Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil?
Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root)
-Settings.artifacts['max_size'] ||= 100 # in megabytes
+Settings.artifacts['max_size'] ||= 100 # in megabytes
+
+#
+# Registry
+#
+Settings['registry'] ||= Settingslogic.new({})
+Settings.registry['enabled'] ||= false
+Settings.registry['host'] ||= "example.com"
+Settings.registry['port'] ||= nil
+Settings.registry['api_url'] ||= "http://localhost:5000/"
+Settings.registry['key'] ||= nil
+Settings.registry['issuer'] ||= nil
+Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':')
+Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root)
#
# Git LFS
@@ -298,7 +312,7 @@ Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['pg_schema'] = nil
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
-Settings.backup['archive_permissions'] ||= 0600
+Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Convert upload connection settings to use symbol keys, to make Fog happy
if Settings.backup['upload']['connection']
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 80d641d73a3..e026151a032 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,11 +1,11 @@
# GIT over HTTP
-require Rails.root.join("lib", "gitlab", "backend", "grack_auth")
+require_dependency Rails.root.join('lib/gitlab/backend/grack_auth')
# GIT over SSH
-require Rails.root.join("lib", "gitlab", "backend", "shell")
+require_dependency Rails.root.join('lib/gitlab/backend/shell')
# GitLab shell adapter
-require Rails.root.join("lib", "gitlab", "backend", "shell_adapter")
+require_dependency Rails.root.join('lib/gitlab/backend/shell_adapter')
required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index df28d30d750..1933afcbfb1 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -2,7 +2,7 @@ CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
aws_file = Rails.root.join('config', 'aws.yml')
-if File.exists?(aws_file)
+if File.exist?(aws_file)
AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env]
CarrierWave.configure do |config|
@@ -20,7 +20,7 @@ if File.exists?(aws_file)
config.fog_public = false
# optional, defaults to {}
- config.fog_attributes = { 'Cache-Control'=>'max-age=315576000' }
+ config.fog_attributes = { 'Cache-Control' => 'max-age=315576000' }
# optional time (in seconds) that authenticated urls will be valid.
# when fog_public is false and provider is AWS or Google, defaults to 600
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 31dceaebcad..021bdb11251 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -243,7 +243,7 @@ Devise.setup do |config|
when Hash
# Add procs for handling SLO
if provider['name'] == 'cas3'
- provider['args'][:on_single_sign_out] = lambda do |request|
+ provider['args'][:on_single_sign_out] = lambda do |request|
ticket = request.params[:session_index]
raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket)
Gitlab::OAuth::Session.destroy(:cas3, ticket)
diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb
deleted file mode 100644
index 05a1852cdbd..00000000000
--- a/config/initializers/devise_async.rb
+++ /dev/null
@@ -1 +0,0 @@
-Devise::Async.backend = :sidekiq
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 66ac88e9f4a..7bd13105045 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -52,7 +52,7 @@ Doorkeeper.configure do
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :api
- #optional_scopes :write, :update
+ # optional_scopes :write, :update
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
@@ -71,7 +71,7 @@ Doorkeeper.configure do
# The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
# (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
#
- native_redirect_uri nil#'urn:ietf:wg:oauth:2.0:oob'
+ native_redirect_uri nil # 'urn:ietf:wg:oauth:2.0:oob'
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
new file mode 100644
index 00000000000..79e2d23ab2e
--- /dev/null
+++ b/config/initializers/health_check.rb
@@ -0,0 +1,3 @@
+HealthCheck.setup do |config|
+ config.standard_checks = ['database', 'migrations', 'cache']
+end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index b2d08d87bac..0c788714714 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -12,6 +12,7 @@ if Gitlab::Metrics.enabled?
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
+ config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
end
Sidekiq.configure_server do |config|
@@ -118,6 +119,8 @@ if Gitlab::Metrics.enabled?
# Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki)
+
+ config.instrument_instance_methods(API::Helpers)
end
GC::Profiler.enable
diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
deleted file mode 100644
index 62b05a55285..00000000000
--- a/config/initializers/monkey_patch.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
-## https://github.com/rails/rails/issues/21108
-
-module ActiveRecord
- module ConnectionAdapters
- class AbstractMysqlAdapter < AbstractAdapter
- # SHOW VARIABLES LIKE 'name'
- def show_variable(name)
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
- variables.first['Value'] unless variables.empty?
- rescue ActiveRecord::StatementInvalid
- nil
- end
-
-
- # MySQL is too stupid to create a temporary table for use subquery, so we have
- # to give it some prompting in the form of a subsubquery. Ugh!
- def subquery_for(key, select)
- subsubselect = select.clone
- subsubselect.projections = [key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
- end
- end
- end
-end
-
-module ActiveRecord
- module ConnectionAdapters
- class MysqlAdapter < AbstractMysqlAdapter
- ADAPTER_NAME = 'MySQL'.freeze
-
- # Get the client encoding for this database
- def client_encoding
- return @client_encoding if @client_encoding
-
- result = exec_query(
- "select @@character_set_client",
- 'SCHEMA')
- @client_encoding = ENCODINGS[result.rows.last.last]
- end
- end
- end
-end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index 4c164119fff..26c30e523a7 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -13,7 +13,7 @@ end
OmniAuth.config.full_host = Settings.gitlab['base_url']
OmniAuth.config.allowed_request_methods = [:post]
-#In case of auto sign-in, the GET method is used (users don't get to click on a button)
+# In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env|
OmniAuth::RequestForgeryProtection.call(env)
diff --git a/config/initializers/premailer.rb b/config/initializers/premailer.rb
index b9176688bc4..cb00d3cfe95 100644
--- a/config/initializers/premailer.rb
+++ b/config/initializers/premailer.rb
@@ -3,6 +3,6 @@ Premailer::Rails.config.merge!(
generate_text_part: false,
preserve_styles: true,
remove_comments: true,
- remove_ids: true,
+ remove_ids: false,
remove_scripts: false
)
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 599dabb9e50..0d9d87bac00 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -23,6 +23,6 @@ else
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
- path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
+ 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 dafecc94648..428302d0fd7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -56,6 +56,7 @@ Rails.application.routes.draw do
# Autocomplete
get '/autocomplete/users' => 'autocomplete#users'
get '/autocomplete/users/:id' => 'autocomplete#user'
+ get '/autocomplete/projects' => 'autocomplete#projects'
# Emojis
resources :emojis, only: :index
@@ -64,6 +65,9 @@ Rails.application.routes.draw do
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
+ # JSON Web Token
+ get 'jwt/auth' => 'jwt#auth'
+
# API
API::API.logger Rails.logger
mount API::API => '/api'
@@ -73,6 +77,9 @@ Rails.application.routes.draw do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
+ # Health check
+ get 'health_check(/:checks)' => 'health_check#index', as: :health_check
+
# Enable Grack support
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
@@ -80,7 +87,7 @@ Rails.application.routes.draw do
get 'help' => 'help#index'
get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ }
get 'help/shortcuts'
- get 'help/ui' => 'help#ui'
+ get 'help/ui' => 'help#ui'
#
# Global snippets
@@ -91,7 +98,8 @@ Rails.application.routes.draw do
end
end
- get '/s/:username' => 'snippets#index', as: :user_snippets, constraints: { username: /.*/ }
+ get '/s/:username', to: redirect('/u/%{username}/snippets'),
+ constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
#
# Invites
@@ -253,6 +261,7 @@ Rails.application.routes.draw do
end
resource :logs, only: [:show]
+ resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
@@ -284,6 +293,7 @@ Rails.application.routes.draw do
resource :application_settings, only: [:show, :update] do
resources :services
put :reset_runners_token
+ put :reset_health_check_token
put :clear_repository_check_states
end
@@ -342,23 +352,18 @@ Rails.application.routes.draw do
end
end
- get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
- constraints: { username: /.*/ }
-
- get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities,
- constraints: { username: /.*/ }
-
- get 'u/:username/groups' => 'users#groups', as: :user_groups,
- constraints: { username: /.*/ }
-
- get 'u/:username/projects' => 'users#projects', as: :user_projects,
- constraints: { username: /.*/ }
-
- get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects,
- constraints: { username: /.*/ }
-
- get '/u/:username' => 'users#show', as: :user,
- constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
+ scope(path: 'u/:username',
+ as: :user,
+ constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ },
+ controller: :users) do
+ get :calendar
+ get :calendar_activities
+ get :groups
+ get :projects
+ get :contributed, as: :contributed_projects
+ get :snippets
+ get '/', action: :show
+ end
#
# Dashboard Area
@@ -416,7 +421,11 @@ Rails.application.routes.draw do
resources :projects, constraints: { id: /[^\/]+/ }, only: [:index, :new, :create]
- devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations }
+ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
+ registrations: :registrations,
+ passwords: :passwords,
+ sessions: :sessions,
+ confirmations: :confirmations }
devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
@@ -659,9 +668,16 @@ Rails.application.routes.draw do
end
resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resource :variables, only: [:show, :update]
+ resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :destroy]
+ resources :pipelines, only: [:index, :new, :create, :show] do
+ member do
+ post :cancel
+ post :retry
+ end
+ end
+
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
@@ -672,6 +688,7 @@ Rails.application.routes.draw do
post :cancel
post :retry
post :erase
+ get :trace
get :raw
end
@@ -688,6 +705,8 @@ Rails.application.routes.draw do
end
end
+ resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex }
+
resources :milestones, constraints: { id: /\d+/ } do
member do
put :sort_issues
@@ -774,7 +793,7 @@ Rails.application.routes.draw do
end
# Get all keys of user
- get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
+ get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ }
get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
end
diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
index 8a351cf27a3..6aed0fe03d2 100644
--- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
+++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
@@ -24,11 +24,11 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
def process_projects_with_wrong_url
projects_with_wrong_import_url.each do |project|
begin
- import_url = Gitlab::ImportUrl.new(project["import_url"])
+ import_url = Gitlab::UrlSanitizer.new(project["import_url"])
update_import_url(import_url, project)
update_import_data(import_url, project)
- rescue URI::InvalidURIError
+ rescue Addressable::URI::InvalidURIError
nullify_import_url(project)
end
end
diff --git a/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
index 75de5f70fa2..72b862d67d2 100644
--- a/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
+++ b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
@@ -7,7 +7,9 @@ class AddDefaultGroupVisibilityToApplicationSettings < ActiveRecord::Migration
add_column :application_settings, :default_group_visibility, :integer
# Unfortunately, this can't be a `default`, since we don't want the configuration specific
# `allowed_visibility_level` to end up in schema.rb
- execute("UPDATE application_settings SET default_group_visibility = #{allowed_visibility_level}")
+
+ visibility_level = allowed_visibility_level || Gitlab::VisibilityLevel::PRIVATE
+ execute("UPDATE application_settings SET default_group_visibility = #{visibility_level}")
end
def down
diff --git a/db/migrate/20160407120251_add_images_enabled_for_project.rb b/db/migrate/20160407120251_add_images_enabled_for_project.rb
new file mode 100644
index 00000000000..47f0ca8e8de
--- /dev/null
+++ b/db/migrate/20160407120251_add_images_enabled_for_project.rb
@@ -0,0 +1,5 @@
+class AddImagesEnabledForProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :container_registry_enabled, :boolean
+ end
+end
diff --git a/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
new file mode 100644
index 00000000000..facd33875ba
--- /dev/null
+++ b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddDisabledOauthSignInSourcesToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :disabled_oauth_sign_in_sources, :text
+ end
+end
diff --git a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb
new file mode 100644
index 00000000000..84e5e4eabe2
--- /dev/null
+++ b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb
@@ -0,0 +1,13 @@
+class AddRunUntaggedToCiRunner < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:ci_runners, :run_untagged, :boolean,
+ default: true, allow_null: false)
+ end
+
+ def down
+ remove_column(:ci_runners, :run_untagged)
+ end
+end
diff --git a/db/migrate/20160508215820_add_type_to_notes.rb b/db/migrate/20160508215820_add_type_to_notes.rb
new file mode 100644
index 00000000000..58944d4e651
--- /dev/null
+++ b/db/migrate/20160508215820_add_type_to_notes.rb
@@ -0,0 +1,5 @@
+class AddTypeToNotes < ActiveRecord::Migration
+ def change
+ add_column :notes, :type, :string
+ end
+end
diff --git a/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
new file mode 100644
index 00000000000..c3f23d89d5a
--- /dev/null
+++ b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
@@ -0,0 +1,5 @@
+class SetTypeOnLegacyDiffNotes < ActiveRecord::Migration
+ def change
+ execute "UPDATE notes SET type = 'LegacyDiffNote' WHERE line_code IS NOT NULL"
+ end
+end
diff --git a/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
new file mode 100644
index 00000000000..9d729fec189
--- /dev/null
+++ b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddHealthCheckAccessTokenToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :health_check_access_token, :string
+ end
+end
diff --git a/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
new file mode 100644
index 00000000000..c34e7ba5409
--- /dev/null
+++ b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
@@ -0,0 +1,12 @@
+class AddSendUserConfirmationEmailToApplicationSettings < ActiveRecord::Migration
+ def up
+ add_column :application_settings, :send_user_confirmation_email, :boolean, default: false
+
+ #Sets confirmation email to true by default on existing installations.
+ execute "UPDATE application_settings SET send_user_confirmation_email=true"
+ end
+
+ def down
+ remove_column :application_settings, :send_user_confirmation_email
+ end
+end
diff --git a/db/migrate/20160525205328_remove_main_language_from_projects.rb b/db/migrate/20160525205328_remove_main_language_from_projects.rb
new file mode 100644
index 00000000000..0f9d60c385f
--- /dev/null
+++ b/db/migrate/20160525205328_remove_main_language_from_projects.rb
@@ -0,0 +1,21 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveMainLanguageFromProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ remove_column :projects, :main_language
+ end
+end
diff --git a/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb b/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb
new file mode 100644
index 00000000000..7910120b4e0
--- /dev/null
+++ b/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb
@@ -0,0 +1,13 @@
+class RemoveNotificationSettingsForDeletedProjects < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM notification_settings
+ WHERE notification_settings.source_type = 'Project'
+ AND NOT EXISTS (
+ SELECT *
+ FROM projects
+ WHERE projects.id = notification_settings.source_id
+ )
+ SQL
+ end
+end
diff --git a/db/migrate/20160528043124_add_users_state_index.rb b/db/migrate/20160528043124_add_users_state_index.rb
new file mode 100644
index 00000000000..e77a5460737
--- /dev/null
+++ b/db/migrate/20160528043124_add_users_state_index.rb
@@ -0,0 +1,9 @@
+class AddUsersStateIndex < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :users, :state
+ end
+end
diff --git a/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb b/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb
new file mode 100644
index 00000000000..e21376bd571
--- /dev/null
+++ b/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb
@@ -0,0 +1,9 @@
+# This is ONLINE migration
+
+class AddContainerRegistryTokenExpireDelayToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_column :application_settings, :container_registry_token_expire_delay, :integer, default: 5
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 71d953afe30..b2af810f600 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160508194200) do
+ActiveRecord::Schema.define(version: 20160530150109) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -43,43 +43,47 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "home_page_url"
- t.integer "default_branch_protection", default: 2
+ t.integer "default_branch_protection", default: 2
t.text "restricted_visibility_levels"
- t.boolean "version_check_enabled", default: true
- t.integer "max_attachment_size", default: 10, null: false
+ t.boolean "version_check_enabled", default: true
+ t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
- t.boolean "user_oauth_applications", default: true
+ t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path"
- t.integer "session_expire_delay", default: 10080, null: false
+ t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
t.string "admin_notification_email"
- t.boolean "shared_runners_enabled", default: true, null: false
- t.integer "max_artifacts_size", default: 100, null: false
+ t.boolean "shared_runners_enabled", default: true, null: false
+ t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
- t.boolean "require_two_factor_authentication", default: false
- t.integer "two_factor_grace_period", default: 48
- t.boolean "metrics_enabled", default: false
- t.string "metrics_host", default: "localhost"
- t.integer "metrics_pool_size", default: 16
- t.integer "metrics_timeout", default: 10
- t.integer "metrics_method_call_threshold", default: 10
- t.boolean "recaptcha_enabled", default: false
+ t.boolean "require_two_factor_authentication", default: false
+ t.integer "two_factor_grace_period", default: 48
+ t.boolean "metrics_enabled", default: false
+ t.string "metrics_host", default: "localhost"
+ t.integer "metrics_pool_size", default: 16
+ t.integer "metrics_timeout", default: 10
+ t.integer "metrics_method_call_threshold", default: 10
+ t.boolean "recaptcha_enabled", default: false
t.string "recaptcha_site_key"
t.string "recaptcha_private_key"
- t.integer "metrics_port", default: 8089
- t.boolean "akismet_enabled", default: false
+ 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.integer "metrics_sample_interval", default: 15
+ t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
- t.boolean "email_author_in_body", default: false
+ t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility"
- t.boolean "repository_checks_enabled", default: false
+ t.boolean "repository_checks_enabled", default: false
t.text "shared_runners_text"
- t.integer "metrics_packet_size", default: 1
+ t.integer "metrics_packet_size", default: 1
+ t.text "disabled_oauth_sign_in_sources"
+ t.string "health_check_access_token"
+ t.boolean "send_user_confirmation_email", default: false
+ t.integer "container_registry_token_expire_delay", default: 5
end
create_table "audit_events", force: :cascade do |t|
@@ -267,6 +271,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.string "revision"
t.string "platform"
t.string "architecture"
+ t.boolean "run_untagged", default: true, null: false
end
add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
@@ -634,6 +639,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.text "st_diff"
t.integer "updated_by_id"
t.boolean "is_award", default: false, null: false
+ t.string "type"
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -755,10 +761,10 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.integer "build_timeout", default: 3600, null: false
t.boolean "pending_delete", default: false
t.boolean "public_builds", default: true, null: false
- t.string "main_language"
t.integer "pushes_since_gc", default: 0
t.boolean "last_repository_check_failed"
t.datetime "last_repository_check_at"
+ t.boolean "container_registry_enabled"
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -995,6 +1001,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
+ add_index "users", ["state"], name: "index_users_on_state", using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
diff --git a/doc/README.md b/doc/README.md
index e358da1c424..d1345ab2493 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -13,6 +13,7 @@
- [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
+- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
@@ -41,8 +42,10 @@
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
+- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint
- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs
- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability
+- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab
## Contributor documentation
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
new file mode 100644
index 00000000000..caf9a5bef2c
--- /dev/null
+++ b/doc/administration/container_registry.md
@@ -0,0 +1,375 @@
+# GitLab Container Registry Administration
+
+> **Note:**
+This feature was [introduced][ce-4040] in GitLab 8.8.
+
+With the Docker Container Registry integrated into GitLab, every project can
+have its own space to store its Docker images.
+
+You can read more about Docker Registry at https://docs.docker.com/registry/introduction/.
+
+---
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Enable the Container Registry](#enable-the-container-registry)
+- [Container Registry domain configuration](#container-registry-domain-configuration)
+ - [Configure Container Registry under an existing GitLab domain](#configure-container-registry-under-an-existing-gitlab-domain)
+ - [Configure Container Registry under its own domain](#configure-container-registry-under-its-own-domain)
+- [Disable Container Registry site-wide](#disable-container-registry-site-wide)
+- [Disable Container Registry per project](#disable-container-registry-per-project)
+- [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide)
+- [Container Registry storage path](#container-registry-storage-path)
+- [Storage limitations](#storage-limitations)
+- [Changelog](#changelog)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+## Enable the Container Registry
+
+**Omnibus GitLab installations**
+
+All you have to do is configure the domain name under which the Container
+Registry will listen to. Read [#container-registry-domain-configuration](#container-registry-domain-configuration)
+and pick one of the two options that fits your case.
+
+>**Note:**
+The container Registry works under HTTPS by default. Using HTTP is possible
+but not recommended and out of the scope of this document.
+Read the [insecure Registry documentation][docker-insecure] if you want to
+implement this.
+
+---
+
+**Installations from source**
+
+If you have installed GitLab from source:
+
+1. You will have to [install Docker Registry][registry-deploy] by yourself.
+1. After the installation is complete, you will have to configure the Registry's
+ settings in `gitlab.yml` in order to enable it.
+1. Use the sample NGINX configuration file that is found under
+ [`lib/support/nginx/registry-ssl`][registry-ssl] and edit it to match the
+ `host`, `port` and TLS certs paths.
+
+The contents of `gitlab.yml` are:
+
+```
+registry:
+ enabled: true
+ host: registry.gitlab.example.com
+ port: 5005
+ api_url: http://localhost:5000/
+ key_path: config/registry.key
+ path: shared/registry
+ issuer: gitlab-issuer
+```
+
+where:
+
+| Parameter | Description |
+| --------- | ----------- |
+| `enabled` | `true` or `false`. Enables the Registry in GitLab. By default this is `false`. |
+| `host` | The host URL under which the Registry will run and the users will be able to use. |
+| `port` | The port under which the external Registry domain will listen on. |
+| `api_url` | The internal API URL under which the Registry is exposed to. It defaults to `http://localhost:5000`. |
+| `key_path`| The private key location that is a pair of Registry's `rootcertbundle`. Read the [token auth configuration documentation][token-config]. |
+| `path` | This should be the same directory like specified in Registry's `rootdirectory`. Read the [storage configuration documentation][storage-config]. This path needs to be readable by the GitLab user, the web-server user and the Registry user. Read more in [#container-registry-storage-path](#container-registry-storage-path). |
+| `issuer` | This should be the same value as configured in Registry's `issuer`. Read the [token auth configuration documentation][token-config]. |
+
+>**Note:**
+GitLab does not ship with a Registry init file. Hence, [restarting GitLab][restart gitlab]
+will not restart the Registry should you modify its settings. Read the upstream
+documentation on how to achieve that.
+
+## Container Registry domain configuration
+
+There are two ways you can configure the Registry's external domain.
+
+- Either [use the existing GitLab domain][existing-domain] where in that case
+ the Registry will have to listen on a port and reuse GitLab's TLS certificate,
+- or [use a completely separate domain][new-domain] with a new TLS certificate
+ for that domain.
+
+Since the container Registry requires a TLS certificate, in the end it all boils
+down to how easy or pricey is to get a new one.
+
+Please take this into consideration before configuring the Container Registry
+for the first time.
+
+### Configure Container Registry under an existing GitLab domain
+
+If the Registry is configured to use the existing GitLab domain, you can
+expose the Registry on a port so that you can reuse the existing GitLab TLS
+certificate.
+
+Assuming that the GitLab domain is `https://gitlab.example.com` and the port the
+Registry is exposed to the outside world is `4567`, here is what you need to set
+in `gitlab.rb` or `gitlab.yml` if you are using Omnibus GitLab or installed
+GitLab from source respectively.
+
+---
+
+**Omnibus GitLab installations**
+
+1. Your `/etc/gitlab/gitlab.rb` should contain the Registry URL as well as the
+ path to the existing TLS certificate and key used by GitLab:
+
+ ```ruby
+ registry_external_url 'https://gitlab.example.com:4567'
+ ```
+
+ Note how the `registry_external_url` is listening on HTTPS under the
+ existing GitLab URL, but on a different port.
+
+ If your TLS certificate is not in `/etc/gitlab/ssl/gitlab.example.com.crt`
+ and key not in `/etc/gitlab/ssl/gitlab.example.com.key` uncomment the lines
+ below:
+
+ ```ruby
+ registry_nginx['ssl_certificate'] = "/path/to/certificate.pem"
+ registry_nginx['ssl_certificate_key'] = "/path/to/certificate.key"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
+ configure it with the following settings:
+
+ ```
+ registry:
+ enabled: true
+ host: gitlab.example.com
+ port: 4567
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
+
+---
+
+Users should now be able to login to the Container Registry with their GitLab
+credentials using:
+
+```bash
+docker login gitlab.example.com:4567
+```
+
+### Configure Container Registry under its own domain
+
+If the Registry is configured to use its own domain, you will need a TLS
+certificate for that specific domain (e.g., `registry.example.com`) or maybe
+a wildcard certificate if hosted under a subdomain of your existing GitLab
+domain (e.g., `registry.gitlab.example.com`).
+
+Let's assume that you want the container Registry to be accessible at
+`https://registry.gitlab.example.com`.
+
+---
+
+**Omnibus GitLab installations**
+
+1. Place your TLS certificate and key in
+ `/etc/gitlab/ssl/registry.gitlab.example.com.crt` and
+ `/etc/gitlab/ssl/registry.gitlab.example.com.key` and make sure they have
+ correct permissions:
+
+ ```bash
+ chmod 600 /etc/gitlab/ssl/registry.gitlab.example.com.*
+ ```
+
+1. Once the TLS certificate is in place, edit `/etc/gitlab/gitlab.rb` with:
+
+ ```ruby
+ registry_external_url 'https://registry.gitlab.example.com'
+ ```
+
+ Note how the `registry_external_url` is listening on HTTPS.
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+> **Note:**
+If you have a [wildcard certificate][], you need to specify the path to the
+certificate in addition to the URL, in this case `/etc/gitlab/gitlab.rb` will
+look like:
+>
+```ruby
+registry_nginx['ssl_certificate'] = "/etc/gitlab/ssl/certificate.pem"
+registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/certificate.key"
+```
+
+---
+
+**Installations from source**
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
+ configure it with the following settings:
+
+ ```
+ registry:
+ enabled: true
+ host: registry.gitlab.example.com
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
+
+---
+
+Users should now be able to login to the Container Registry using their GitLab
+credentials:
+
+```bash
+docker login registry.gitlab.example.com
+```
+
+## Disable Container Registry site-wide
+
+>**Note:**
+Disabling the Registry in the Rails GitLab application as set by the following
+steps, will not remove any existing Docker images. This is handled by the
+Registry application itself.
+
+**Omnibus GitLab**
+
+1. Open `/etc/gitlab/gitlab.rb` and set `registry['enable']` to `false`:
+
+ ```ruby
+ registry['enable'] = false
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
+ set `enabled` to `false`:
+
+ ```
+ registry:
+ enabled: false
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Disable Container Registry per project
+
+If Registry is enabled in your GitLab instance, but you don't need it for your
+project, you can disable it from your project's settings. Read the user guide
+on how to achieve that.
+
+## Disable Container Registry for new projects site-wide
+
+If the Container Registry is enabled, then it will be available on all new
+projects. To disable this function and let the owners of a project to enable
+the Container Registry by themselves, follow the steps below.
+
+---
+
+**Omnibus GitLab installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['gitlab_default_projects_features_container_registry'] = false
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, find the `default_projects_features`
+ entry and configure it so that `container_registry` is set to `false`:
+
+ ```
+ ## Default project features settings
+ default_projects_features:
+ issues: true
+ merge_requests: true
+ wiki: true
+ snippets: false
+ builds: true
+ container_registry: false
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Container Registry storage path
+
+To change the storage path where Docker images will be stored, follow the
+steps below.
+
+This path is accessible to:
+
+- the user running the Container Registry daemon,
+- the user running GitLab
+
+> **Warning** You should confirm that all GitLab, Registry and web server users
+have access to this directory.
+
+---
+
+**Omnibus GitLab installations**
+
+The default location where images are stored in Omnibus, is
+`/var/opt/gitlab/gitlab-rails/shared/registry`. To change it:
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['registry_path'] = "/path/to/registry/storage"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+The default location where images are stored in source installations, is
+`/home/git/gitlab/shared/registry`. To change it:
+
+1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
+ change the `path` setting:
+
+ ```
+ registry:
+ path: shared/registry
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Storage limitations
+
+Currently, there is no storage limitation, which means a user can upload an
+infinite amount of Docker images with arbitrary sizes. This setting will be
+configurable in future releases.
+
+## Changelog
+
+**GitLab 8.8 ([source docs][8-8-docs])**
+
+- GitLab Container Registry feature was introduced.
+
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart gitlab]: restart_gitlab.md#installations-from-source
+[wildcard certificate]: https://en.wikipedia.org/wiki/Wildcard_certificate
+[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
+[docker-insecure]: https://docs.docker.com/registry/insecure/
+[registry-deploy]: https://docs.docker.com/registry/deploying/
+[storage-config]: https://docs.docker.com/registry/configuration/#storage
+[token-config]: https://docs.docker.com/registry/configuration/#token
+[8-8-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/doc/administration/container_registry.md
+[registry-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/registry-ssl
+[existing-domain]: #configure-container-registry-under-an-existing-gitlab-domain
+[new-domain]: #configure-container-registry-under-its-own-domain
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 43ab153d76d..7f53915a4d7 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -58,4 +58,4 @@ to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
It's possible to preconfigure the GitLab docker image by adding the environment
variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
-For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
+For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://docs.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 43d85ffb775..d74a786ac24 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -19,6 +19,8 @@ Components/Servers Required:
- 2 servers/virtual machines (one active/one passive)
+![Active/Passive HA Diagram](../img/high_availability/active-passive-diagram.png)
+
### Active/Active
This architecture scales easily because all application servers handle
@@ -26,6 +28,8 @@ user requests simultaneously. The database, Redis, and GitLab application are
all deployed on separate servers. The configuration is **only** highly-available
if the database, Redis and storage are also configured as such.
+![Active/Active HA Diagram](../img/high_availability/active-active-diagram.png)
+
**Steps to configure active/active:**
1. [Configure the database](database.md)
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index b1fe34ed9a1..136f570ac27 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -60,4 +60,4 @@ Read more on high-availability configuration:
configure custom domains with custom SSL, which would not be possible
if SSL was terminated at the load balancer.
-[gitlab-pages]: http://doc.gitlab.com/ee/pages/administration.html
+[gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index e4e124e200a..537f4f3501d 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -2,8 +2,8 @@
## Required NFS Server features
-**File locking**: GitLab **requires** file locking which is only supported
-natively in NFS version 4. NFSv3 also supports locking as long as
+**File locking**: GitLab **requires** advisory file locking, which is only
+supported natively in NFS version 4. NFSv3 also supports locking as long as
Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
specifically test NFSv3.
@@ -113,4 +113,4 @@ Read more on high-availability configuration:
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
-[udp-log-shipping]: http://doc.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
+[udp-log-shipping]: http://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index d89a1e582ca..f6153216f33 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -26,7 +26,7 @@ that runs Redis.
```ruby
external_url 'https://gitlab.example.com'
- # Disable all components except PostgreSQL
+ # Disable all components except Redis
redis['enable'] = true
bootstrap['enable'] = false
nginx['enable'] = false
diff --git a/doc/administration/img/high_availability/active-active-diagram.png b/doc/administration/img/high_availability/active-active-diagram.png
new file mode 100644
index 00000000000..81259e0ae93
--- /dev/null
+++ b/doc/administration/img/high_availability/active-active-diagram.png
Binary files differ
diff --git a/doc/administration/img/high_availability/active-passive-diagram.png b/doc/administration/img/high_availability/active-passive-diagram.png
new file mode 100644
index 00000000000..f69ff1d0357
--- /dev/null
+++ b/doc/administration/img/high_availability/active-passive-diagram.png
Binary files differ
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 3411e4af6a7..4172b604cec 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -5,7 +5,7 @@ This feature was [introduced][ce-3232] in GitLab 8.7. It is OFF by
default because it still causes too many false alarms.
Git has a built-in mechanism, [git fsck][git-fsck], to verify the
-integrity of all data commited to a repository. GitLab administrators
+integrity of all data committed to a repository. GitLab administrators
can trigger such a check for a project via the project page under the
admin panel. The checks run asynchronously so it may take a few minutes
before the check result is visible on the project admin page. If the
@@ -41,4 +41,4 @@ alarms you can choose to clear ALL repository check states from the
---
[ce-3232]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3232 "Auto git fsck"
-[git-fsck]: https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html "git fsck documentation" \ No newline at end of file
+[git-fsck]: https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html "git fsck documentation"
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 134a7583762..a776cd3f05e 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -150,6 +150,14 @@ To output a backtrace from all threads at once:
apply all thread bt
```
+Once you're done debugging with `gdb`, be sure to detach from the process and
+exit:
+
+```
+detach
+exit
+```
+
## Check for blocking queries
Sometimes the speed at which Sidekiq processes jobs can be so fast that it can
diff --git a/doc/api/README.md b/doc/api/README.md
index ff039f1886f..27c5962decf 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -33,7 +33,7 @@ following locations:
- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
- [Runners](runners.md)
-- [Licenses](licenses.md)
+- [Open source license templates](licenses.md)
## Authentication
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 4a12e962b62..0881a7d7a90 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -101,8 +101,18 @@ DELETE /projects/:id/triggers/:token
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
-| `token` | string | yes | The `token` of a project |
+| `token` | string | yes | The `token` of a trigger |
```
curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
+
+```json
+{
+ "created_at": "2015-12-23T16:25:56.760Z",
+ "deleted_at": "2015-12-24T12:32:20.100Z",
+ "last_used": null,
+ "token": "7b9148c158980bbd9bcea92c17522d",
+ "updated_at": "2015-12-24T12:32:20.100Z"
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 2821bc21b81..1ccb9715e96 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -265,7 +265,6 @@ GET /groups/:id/members
{
"id": 1,
"username": "raymond_smith",
- "email": "ray@smith.org",
"name": "Raymond Smith",
"state": "active",
"created_at": "2012-10-22T14:13:35Z",
@@ -274,7 +273,6 @@ GET /groups/:id/members
{
"id": 2,
"username": "john_doe",
- "email": "joh@doe.org",
"name": "John Doe",
"state": "active",
"created_at": "2012-10-22T14:13:35Z",
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 3e78149f442..fc7a7ae0c0c 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -77,7 +77,8 @@ Example response:
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : [],
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -154,7 +155,8 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -216,7 +218,8 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed": false
+ "subscribed": false,
+ "user_notes_count": 1
}
```
@@ -271,7 +274,8 @@ Example response:
"description" : null,
"updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null,
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
@@ -329,7 +333,8 @@ Example response:
"id" : 85,
"assignee" : null,
"milestone" : null,
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 3730c07c5a7..a181c0f57a2 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -39,7 +39,7 @@ Example response:
{
"name" : "critical",
"color" : "#d9534f",
- "description": "Criticalissue. Need fix ASAP",
+ "description": "Critical issue. Need fix ASAP",
"open_issues_count": 1,
"closed_issues_count": 3,
"open_merge_requests_count": 1
@@ -165,3 +165,73 @@ Example response:
"description": "Documentation"
}
```
+
+## Subscribe to a label
+
+Subscribes the authenticated user to a label to receive notifications. If the
+operation is successful, status code `201` together with the updated label is
+returned. If the user is already subscribed to the label, the status code `304`
+is returned. If the project or label is not found, status code `404` is
+returned.
+
+```
+POST /projects/:id/labels/:label_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------ |
+| `id` | integer | yes | The ID of a project |
+| `label_id` | integer or string | yes | The ID or title of a project's label |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+```
+
+Example response:
+
+```json
+{
+ "name": "Docs",
+ "color": "#cc0033",
+ "description": "",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": true
+}
+```
+
+## Unsubscribe from a label
+
+Unsubscribes the authenticated user from a label to not receive notifications
+from it. If the operation is successful, status code `200` together with the
+updated label is returned. If the user is not subscribed to the label, the
+status code `304` is returned. If the project or label is not found, status code
+`404` is returned.
+
+```
+DELETE /projects/:id/labels/:label_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------ |
+| `id` | integer | yes | The ID of a project |
+| `label_id` | integer or string | yes | The ID or title of a project's label |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+```
+
+Example response:
+
+```json
+{
+ "name": "Docs",
+ "color": "#cc0033",
+ "description": "",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2057f9d77aa..8217e30fe25 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -67,7 +67,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -130,7 +131,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -230,6 +232,7 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "user_notes_count": 1,
"changes": [
{
"old_path": "VERSION",
@@ -308,7 +311,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
@@ -378,7 +382,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -472,7 +477,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -537,7 +543,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -602,7 +609,8 @@ Example response:
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
- "labels" : []
+ "labels" : [],
+ "user_notes_count": 1
},
]
```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index a6b5b1787fd..7aa1c2155bf 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 IID of an issue (not ID)
+- `issue_id` (required) - The ID of an issue
```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 IID of a project issue (not ID)
+- `issue_id` (required) - The ID of a project issue
- `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 IID of an issue (not ID)
+- `issue_id` (required) - The ID of an issue
- `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 IID of an issue (not ID)
+- `issue_id` (required) - The ID of an issue
- `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 IID of an issue |
+| `issue_id` | integer | yes | The ID of an issue |
| `note_id` | integer | yes | The ID of a note |
```bash
diff --git a/doc/api/projects.md b/doc/api/projects.md
index de1faadebf5..f5f195b97df 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -424,6 +424,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -447,6 +448,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -472,6 +474,7 @@ Parameters:
- `builds_enabled` (optional)
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
+- `container_registry_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `public_builds` (optional)
diff --git a/doc/api/runners.md b/doc/api/runners.md
index cc6c6b7cb2f..ddfa298f79d 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -275,7 +275,7 @@ POST /projects/:id/runners
| `runner_id` | integer | yes | The ID of a runner |
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" -F "runner_id=9"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" -F "runner_id=9"
```
Example response:
@@ -306,7 +306,7 @@ DELETE /projects/:id/runners/:runner_id
| `runner_id` | integer | yes | The ID of a runner |
```
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9"
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners/9"
```
Example response:
diff --git a/doc/api/services.md b/doc/api/services.md
index 7d45b2cf463..ccfc0fccb7f 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -16,8 +16,8 @@ PUT /projects/:id/services/asana
Parameters:
-- `api_key` (**required**) - User API token. User must have access to task,all comments will be attributed to this user.
-- `restrict_to_branch` (optional) - Comma-separated list of branches which will beautomatically inspected. Leave blank to include all branches.
+- `api_key` (**required**) - User API token. User must have access to task, all comments will be attributed to this user.
+- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.
### Delete Asana service
@@ -491,7 +491,7 @@ Jira issue tracker
Set JIRA service for a project.
-> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)
+> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://docs.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://docs.gitlab.com/ee/integration/jira.html)
```
PUT /projects/:id/services/jira
@@ -503,6 +503,8 @@ Parameters:
- `project_url` (**required**) - Project url
- `issues_url` (**required**) - Issue url
- `description` (optional) - Jira issue tracker
+- `username` (optional) - Jira username
+- `password` (optional) - Jira password
### Delete JIRA service
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 1e745115dc8..43a0fe35e42 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -37,7 +37,8 @@ Example response:
"created_at" : "2016-01-04T15:44:55.176Z",
"default_project_visibility" : 0,
"gravatar_enabled" : true,
- "sign_in_text" : null
+ "sign_in_text" : null,
+ "container_registry_token_expire_delay": 5
}
```
@@ -64,6 +65,7 @@ PUT /application/settings
| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
| `after_sign_out_path` | string | no | Where to redirect users after logout |
+| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
@@ -90,6 +92,7 @@ Example response:
"default_snippet_visibility": 0,
"restricted_signup_domains": [],
"user_oauth_applications": true,
- "after_sign_out_path": ""
+ "after_sign_out_path": "",
+ "container_registry_token_expire_delay": 5
}
```
diff --git a/doc/ci/deployment/README.md b/doc/ci/examples/deployment/README.md
index 7d91ce6710f..7d91ce6710f 100644
--- a/doc/ci/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 6a42a935abd..386b8e29fcf 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -212,8 +212,8 @@ If you want to receive e-mail notifications about the result status of the
builds, you should explicitly enable the **Builds Emails** service under your
project's settings.
-For more information read the [Builds emails service documentation]
-(../../project_services/builds_emails.md).
+For more information read the
+[Builds emails service documentation](../../project_services/builds_emails.md).
## Builds badge
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index a06650b3387..b42d7a62ebc 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -125,7 +125,13 @@ shared runners will only run the jobs they are equipped to run.
For instance, at GitLab we have runners tagged with "rails" if they contain
the appropriate dependencies to run Rails test suites.
-### Be Careful with Sensitive Information
+### Prevent runner with tags from picking jobs without tags
+
+You can configure a runner to prevent it from picking jobs with tags when
+the runnner does not have tags assigned. This setting is available on each
+runner in *Project Settings* > *Runners*.
+
+### Be careful with sensitive information
If you can run a build on a runner, you can get access to any code it runs
and get the token of the runner. With shared runners, this means that anyone
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 79ed512aabb..5c316510d0e 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -33,7 +33,7 @@ POST /projects/:id/trigger/builds
The required parameters are the trigger's `token` and the Git `ref` on which
the trigger will be performed. Valid refs are the branch, the tag or the commit
-SHA. The `:id` of a project can be found by [querying the API](../api/projects.md)
+SHA. The `:id` of a project can be found by [querying the API](../../api/projects.md)
or by visiting the **Triggers** page which provides self-explanatory examples.
When a rebuild is triggered, the information is exposed in GitLab's UI under
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 7e9bced7616..a3481f58c6c 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -128,7 +128,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
### after_script
>**Note:**
-Introduced in GitLab 8.7 and GitLab Runner v1.2.
+Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released)
`after_script` is used to define the command that will be run after for all
builds. This has to be an array or a multi-line string.
@@ -348,7 +348,7 @@ job_name:
| allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
| when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` |
| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them|
-| artifacts | no | Define list build artifacts |
+| artifacts | no | Define list of build artifacts |
| cache | no | Define list of files that should be cached between subsequent runs |
| before_script | no | Override a set of commands that are executed before build |
| after_script | no | Override a set of commands that are executed after build |
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
new file mode 100644
index 00000000000..4df24ef13cc
--- /dev/null
+++ b/doc/container_registry/README.md
@@ -0,0 +1,113 @@
+# GitLab Container Registry
+
+> **Note:**
+This feature was [introduced][ce-4040] in GitLab 8.8.
+
+> **Note:**
+This document is about the user guide. To learn how to enable GitLab Container
+Registry across your GitLab instance, visit the
+[administrator documentation](../administration/container_registry.md).
+
+With the Docker Container Registry integrated into GitLab, every project can
+have its own space to store its Docker images.
+
+You can read more about Docker Registry at https://docs.docker.com/registry/introduction/.
+
+---
+
+## Enable the Container Registry for your project
+
+1. First, ask your system administrator to enable GitLab Container Registry
+ following the [administration documentation](../administration/container_registry.md).
+ If you are using GitLab.com, this is enabled by default so you can start using
+ the Registry immediately.
+
+1. Go to your project's settings and enable the **Container Registry** feature
+ on your project. For new projects this might be enabled by default. For
+ existing projects you will have to explicitly enable it.
+
+ ![Enable Container Registry](img/project_feature.png)
+
+## Build and push images
+
+After you save your project's settings, you should see a new link in the
+sidebar called **Container Registry**. Following this link will get you to
+your project's Registry panel where you can see how to login to the Container
+Registry using your GitLab credentials.
+
+For example if the Registry's URL is `registry.example.com`, the you should be
+able to login with:
+
+```
+docker login registry.example.com
+```
+
+Building and publishing images should be a straightforward process. Just make
+sure that you are using the Registry URL with the namespace and project name
+that is hosted on GitLab:
+
+```
+docker build -t registry.example.com/group/project .
+docker push registry.example.com/group/project
+```
+
+## Use images from GitLab Container Registry
+
+To download and run a container from images hosted in GitLab Container Registry,
+use `docker run`:
+
+```
+docker run [options] registry.example.com/group/project [arguments]
+```
+
+For more information on running Docker containers, visit the
+[Docker documentation][docker-docs].
+
+## Control Container Registry from within GitLab
+
+GitLab offers a simple Container Registry management panel. Go to your project
+and click **Container Registry** in the left sidebar.
+
+This view will show you all tags in your project and will easily allow you to
+delete them.
+
+![Container Registry panel](img/container_registry.png)
+
+## Build and push images using GitLab CI
+
+> **Note:**
+This feature requires GitLab 8.8 and GitLab Runner 1.2.
+
+Make sure that your GitLab Runner is configured to allow building docker images.
+You have to check the [Using Docker Build documentation](../../ci/docker/using_docker_build.md).
+
+You can use [docker:dind](https://hub.docker.com/_/docker/) to build your images,
+and this is how `.gitlab-ci.yml` should look like:
+
+```
+ build_image:
+ image: docker:git
+ services:
+ - docker:dind
+ stage: build
+ script:
+ - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.example.com
+ - docker build -t registry.example.com/group/project:latest .
+ - docker push registry.example.com/group/project:latest
+```
+
+You have to use the credentials of the special `gitlab-ci-token` user with its
+password stored in `$CI_BUILD_TOKEN` in order to push to the Registry connected
+to your project. This allows you to automated building and deployment of your
+Docker images.
+
+## Limitations
+
+In order to use a container image from your private project as an `image:` in
+your `.gitlab-ci.yml`, you have to follow the
+[Using a private Docker Registry][private-docker]
+documentation. This workflow will be simplified in the future.
+
+[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
+[docker-docs]: https://docs.docker.com/engine/userguide/intro/
+[private-docker]: 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/container_registry/img/container_registry.png b/doc/container_registry/img/container_registry.png
new file mode 100644
index 00000000000..e9505a73b40
--- /dev/null
+++ b/doc/container_registry/img/container_registry.png
Binary files differ
diff --git a/doc/container_registry/img/project_feature.png b/doc/container_registry/img/project_feature.png
new file mode 100644
index 00000000000..57a73d253c0
--- /dev/null
+++ b/doc/container_registry/img/project_feature.png
Binary files differ
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 187ec9e7b75..8292b393757 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -127,7 +127,7 @@ Inside the document:
```
If the document you are editing resides in a place other than the GitLab CE/EE
`doc/` directory, instead of the relative link, use the full path:
- `http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
+ `http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate.
## Installation guide
@@ -266,5 +266,5 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
-[gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
+[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index c1cf2e77c26..9168c70945a 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -1,12 +1,125 @@
# Instrumenting Ruby Code
-GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby
-code. This can be used to measure the time spent in a specific part of a larger
-chunk of code. The resulting data is stored as a field in the transaction that
-executed the block.
+GitLab Performance Monitoring allows instrumenting of both methods and custom
+blocks of Ruby code. Method instrumentation is the primary form of
+instrumentation with block-based instrumentation only being used when we want to
+drill down to specific regions of code within a method.
-To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure`
-and give it a name:
+## Instrumenting Methods
+
+Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation`
+module. This module offers a few different methods that can be used to
+instrument code:
+
+* `instrument_method`: instruments a single class method.
+* `instrument_instance_method`: instruments a single instance method.
+* `instrument_class_hierarchy`: given a Class this method will recursively
+ instrument all sub-classes (both class and instance methods).
+* `instrument_methods`: instruments all public class methods of a Module.
+* `instrument_instance_methods`: instruments all public instance methods of a
+ Module.
+
+To remove the need for typing the full `Gitlab::Metrics::Instrumentation`
+namespace you can use the `configure` class method. This method simply yields
+the supplied block while passing `Gitlab::Metrics::Instrumentation` as its
+argument. An example:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_method(Foo, :bar)
+ conf.instrument_method(Foo, :baz)
+end
+```
+
+Using this method is in general preferred over directly calling the various
+instrumentation methods.
+
+Method instrumentation should be added in the initializer
+`config/initializers/metrics.rb`.
+
+### Examples
+
+Instrumenting a single method:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_method(User, :find_by)
+end
+```
+
+Instrumenting an entire class hierarchy:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_class_hierarchy(ActiveRecord::Base)
+end
+```
+
+Instrumenting all public class methods:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_methods(User)
+end
+```
+
+### Checking Instrumented Methods
+
+The easiest way to check if a method has been instrumented is to check its
+source location. For example:
+
+```
+method = Rugged::TagCollection.instance_method(:[])
+
+method.source_location
+```
+
+If the source location points to `lib/gitlab/metrics/instrumentation.rb` you
+know the method has been instrumented.
+
+If you're using Pry you can use the `$` command to display the source code of a
+method (along with its source location), this is easier than running the above
+Ruby code. In case of the above snippet you'd run the following:
+
+```
+$ Rugged::TagCollection#[]
+```
+
+This will print out something along the lines of:
+
+```
+From: /path/to/your/gitlab/lib/gitlab/metrics/instrumentation.rb @ line 148:
+Owner: #<Module:0x0055f0865c6d50>
+Visibility: public
+Number of lines: 21
+
+def #{name}(#{args_signature})
+ trans = Gitlab::Metrics::Instrumentation.transaction
+
+ if trans
+ start = Time.now
+ retval = super
+ duration = (Time.now - start) * 1000.0
+
+ if duration >= Gitlab::Metrics.method_call_threshold
+ trans.increment(:method_duration, duration)
+
+ trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
+ { duration: duration },
+ method: #{label.inspect})
+ end
+
+ retval
+ else
+ super
+ end
+end
+```
+
+## Instrumenting Ruby Blocks
+
+Measuring blocks of Ruby code is done by calling `Gitlab::Metrics.measure` and
+passing it a block. For example:
```ruby
Gitlab::Metrics.measure(:foo) do
@@ -14,6 +127,10 @@ Gitlab::Metrics.measure(:foo) do
end
```
+The block is executed and the execution time is stored as a set of fields in the
+currently running transaction. If no transaction is present the block is yielded
+without measuring anything.
+
3 values are measured for a block:
1. The real time elapsed, stored in NAME_real_time.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 28dedf3978c..02e024ca15a 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -8,7 +8,10 @@ In addition, having to take a server offline for a an upgrade small or big is
a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below.
-It's advised to have offline migrations only in major GitLab releases.
+Migrations should not require GitLab installations to be taken offline unless
+_absolutely_ necessary. If a migration requires downtime this should be
+clearly mentioned during the review process as well as being documented in the
+monthly release post.
When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible
@@ -58,6 +61,45 @@ remove_index :namespaces, column: :name if index_exists?(:namespaces, :name)
If you need to add an unique index please keep in mind there is possibility of existing duplicates. If it is possible write a separate migration for handling this situation. It can be just removing or removing with overwriting all references to these duplicates depend on situation.
+When adding an index make sure to use the method `add_concurrent_index` instead
+of the regular `add_index` method. The `add_concurrent_index` method
+automatically creates concurrent indexes when using PostgreSQL, removing the
+need for downtime. To use this method you must disable transactions by calling
+the method `disable_ddl_transaction!` in the body of your migration class like
+so:
+
+```
+class MyMigration < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def change
+
+ end
+end
+```
+
+## Adding Columns With Default Values
+
+When adding columns with default values you should use the method
+`add_column_with_default`. This method ensures the table is updated without
+requiring downtime. This method is not reversible so you must manually define
+the `up` and `down` methods in your migration class.
+
+For example, to add the column `foo` to the `projects` table with a default
+value of `10` you'd write the following:
+
+```
+class MyMigration < ActiveRecord::Migration
+ def up
+ add_column_with_default(:projects, :foo, :integer, 10)
+ end
+
+ def down
+ remove_column(:projects, :foo)
+ end
+end
+```
+
## Testing
Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
@@ -74,7 +116,7 @@ Example with Arel:
users = Arel::Table.new(:users)
users.group(users[:user_id]).having(users[:id].count.gt(5))
-#updtae other tables with this results
+#update other tables with these results
```
Example with plain SQL and `quote_string` helper:
@@ -89,4 +131,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end
-``` \ No newline at end of file
+```
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 33eed29ba5c..513457d203a 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -65,7 +65,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at
- 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`.
+- Prefer `not_to` to `to_not` (_this is enforced by Rubocop_).
- 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
to separate phases.
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index a3e260a5f89..23760a14b39 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -6,3 +6,51 @@ We created a page inside GitLab where you can check commonly used html and css e
When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples
you can use during GitLab development.
+
+## Design repository
+
+All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design)
+repository and maintained by GitLab UX designers.
+
+## Navigation
+
+GitLab's layout contains 2 sections: the left sidebar and the content. The left sidebar contains a static navigation menu.
+This menu will be visible regardless of what page you visit. The left sidebar also contains the GitLab logo
+and the current user's profile picture. The content section contains a header and the content itself.
+The header describes the current GitLab page and what navigation is
+available to user in this area. Depending on the area (project, group, profile setting) the header name and navigation may change. For example when user visits one of the
+project pages the header will contain a project name and navigation for that project. When the user visits a group page it will contain a group name and navigation related to this group.
+
+### Adding new tab to header navigation
+
+We try to keep the amount of tabs in the header navigation between 5 and 10 so that it fits on a typical laptop screen. We also try not to confuse the user with too many options. Ideally each
+tab should represent separate functionality. Everything related to the issue
+tracker should be under the 'Issues' tab while everything related to the wiki should
+be under 'Wiki' tab and so on and so forth.
+
+## Mobile screen size
+
+We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide
+part of the UI for smaller resolutions in favor of a better user experience.
+However core functionality like browsing files, creating issues, writing comments, should
+be available on all resolutions.
+
+## Icons
+
+* `trash` icon for button or link that does destructive action like removing
+information from database or file system
+* `x` icon for closing/hiding UI element. For example close modal window
+* `pencil` icon for edit button or link
+* `eye` icon for subscribe action
+* `rss` for rss/atom feed
+* `plus` for link or dropdown that lead to page where you create new object (For example new issue page)
+
+
+## Buttons
+
+* Button should contain icon or text. Exceptions should be approved by UX designer.
+* Use gray button on white background or white button on gray background.
+* Use red button for destructive actions (not revertable). For example removing issue.
+* Use green or blue button for primary action. Primary button should be only one.
+Do not use both green and blue button in one form.
+
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index 87f078def04..5221d85b661 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -24,4 +24,4 @@ You may assign the Issue to a user, add a milestone and add labels (they are all
![Submit new issue](basicsimages/submit_new_issue.png)
-Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://doc.gitlab.com/ce/customization/issue_closing.html).
+Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://docs.gitlab.com/ce/customization/issue_closing.html).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index b545d62549d..f737dffc024 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -14,7 +14,7 @@ Fill out the required information:
1. Select a [visibility level](https://gitlab.com/help/public_access/public_access)
-1. You can also [import your existing projects](http://doc.gitlab.com/ce/workflow/importing/README.html)
+1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html)
1. Click on "create project"
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index dcdf49d3379..820934f97f1 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
-Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
+Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index e3af3022262..1318b3d1fa5 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -6,7 +6,7 @@ Since an installation from source is a lot of work and error prone we strongly r
One reason the Omnibus package is more reliable is its use of Runit to restart any of the GitLab processes in case one crashes.
On heavily used GitLab instances the memory usage of the Sidekiq background worker will grow over time.
-Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://doc.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory.
+Omnibus packages solve this by [letting the Sidekiq terminate gracefully](http://docs.gitlab.com/ce/operations/sidekiq_memory_killer.html) if it uses too much memory.
After this termination Runit will detect Sidekiq is not running and will start it.
Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time.
@@ -269,9 +269,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-8-stable gitlab
-**Note:** You can change `8-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -394,7 +394,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout v0.7.1
+ sudo -u git -H git checkout v0.7.4
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 0245febfcd8..44d2a14f366 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -132,5 +132,5 @@ To disable the relative URL:
1. Follow the same as above starting from 2. and set up the
GitLab URL to one that doesn't contain a relative path.
-[omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
+[omnibus-rel]: http://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index df8e8bdc476..09c6211b3ab 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -81,7 +81,7 @@ errors during usage.
- 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
+enough available RAM. Having swap will help reduce the chance of errors occurring
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.
@@ -150,3 +150,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- Internet Explorer (IE) 11+ but please make sure that you have the `Compatibility View` mode disabled.
+- Edge (Latest stable version)
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 6fe04aa2a06..fd330dd7a7d 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -19,7 +19,7 @@ See the documentation below for details on how to configure these services.
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
-[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
+[jenkins]: http://docs.gitlab.com/ee/integration/jenkins.html
## Project services
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index e6b2071f193..e34e306f9ac 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -27,17 +27,18 @@ To enable the CAS OmniAuth provider you must register your application with your
```ruby
gitlab_rails['omniauth_providers'] = [
{
- name: "cas3",
- label: "cas",
- args: {
- url: 'CAS_SERVER',
- login_url: '/CAS_PATH/login',
- service_validate_url: '/CAS_PATH/p3/serviceValidate',
- logout_url: '/CAS_PATH/logout'} }
- }
+ "name"=> "cas3",
+ "label"=> "cas",
+ "args"=> {
+ "url"=> 'CAS_SERVER',
+ "login_url"=> '/CAS_PATH/login',
+ "service_validate_url"=> '/CAS_PATH/p3/serviceValidate',
+ "logout_url"=> '/CAS_PATH/logout'
+ }
}
]
```
+
For installations from source:
@@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Save the configuration file.
+1. Run `gitlab-ctl reconfigure` for the omnibus package.
+
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a CAS tab in the sign in form.
diff --git a/doc/integration/google.md b/doc/integration/google.md
index f9a20dd840d..82978b68a34 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -11,9 +11,9 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Project ID: Must be unique to all Google Developer registered applications. Google provides a randomly generated Project ID by default. You can use the randomly generated ID or choose a new one.
1. Refresh the page. You should now see your new project in the list. Click on the project.
-1. Select "APIs & auth" in the left menu.
+1. Select the "Google APIs" tab in the Overview.
-1. Select "APIs" in the submenu.
+1. Select and enable the following Google APIs - listed under "Popular APIs"
- Enable `Contacts API`
- Enable `Google+ API`
diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png
new file mode 100644
index 00000000000..95f8bbdcd24
--- /dev/null
+++ b/doc/integration/img/enabled-oauth-sign-in-sources.png
Binary files differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index cab329c0dec..820f40f81a9 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -11,6 +11,7 @@ of the configured mechanisms.
- [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
+- [Enable or disable Sign In with an OmniAuth provider without disabling import sources](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources)
## Supported Providers
@@ -191,3 +192,17 @@ experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/w
While we can't officially support every possible authentication mechanism out there,
we'd like to at least help those with specific needs.
+
+## Enable or disable Sign In with an OmniAuth provider without disabling import sources
+
+>**Note:**
+This setting was introduced with version 8.8 of GitLab.
+
+Administrators are able to enable or disable Sign In via some OmniAuth providers.
+
+>**Note:**
+By default Sign In is enabled via all the OAuth Providers that have been configured in `config/gitlab.yml`.
+
+In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings -> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable.
+
+![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources.png)
diff --git a/doc/intro/README.md b/doc/intro/README.md
index ab298d3808e..382d10aaf40 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -39,4 +39,4 @@ Install and update your GitLab installation.
- [Install GitLab](https://about.gitlab.com/installation/)
- [Update GitLab](https://about.gitlab.com/update/)
-- [Explore Omnibus GitLab configuration options](http://doc.gitlab.com/omnibus/settings/configuration.html)
+- [Explore Omnibus GitLab configuration options](http://docs.gitlab.com/omnibus/settings/configuration.html)
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
index 27937e51764..f84060b8d07 100644
--- a/doc/logs/logs.md
+++ b/doc/logs/logs.md
@@ -1,6 +1,6 @@
## Log system
-GitLab has advanced log system so everything is logging and you can analize your instance using various system log files.
-In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html)
+GitLab has an advanced log system where everything is logged so that you can analyze your instance using various system log files.
+In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
@@ -67,13 +67,13 @@ gitlab-shell is using by Gitlab for executing git commands and provide ssh acces
```
I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at </var/opt/gitlab/git-data/repositories/root/dcdcdcdcd.git>.
-I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and simlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
+I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and symlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
```
#### unicorn_stderr.log
This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/log/unicorn_stderr.log` for installations from the source.
-Unicorn is a high-performance forking Web server which is used for serving GitLab application. You can look at this log, for example, if your application does not respond. This log cantains all information about state of unicorn processes at any given time.
+Unicorn is a high-performance forking Web server which is used for serving the GitLab application. You can look at this log if, for example, your application does not respond. This log contains all information about the state of unicorn processes at any given time.
```
I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 4f199b6af6f..236eb7b12c4 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -8,6 +8,7 @@
* [Multiple underscores in words](#multiple-underscores-in-words)
* [URL auto-linking](#url-auto-linking)
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
+* [Inline Diff](#inline-diff)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
* [Task lists](#task-lists)
@@ -153,6 +154,19 @@ s = "There is no highlighting for this."
But let's throw in a <b>tag</b>.
```
+## Inline Diff
+
+With inline diffs tags you can display {+ additions +} or [- deletions -].
+
+The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
+
+However the wrapping tags cannot be mixed as such:
+
+- {+ additions +]
+- [+ additions +}
+- {- deletions -]
+- [- deletions -}
+
## Emoji
Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
@@ -185,20 +199,23 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
-| input | references |
-|:-----------------------|:---------------------------|
-| `@user_name` | specific user |
-| `@group_name` | specific group |
-| `@all` | entire team |
-| `#123` | issue |
-| `!123` | merge request |
-| `$123` | snippet |
-| `~123` | label by ID |
-| `~bug` | one-word label by name |
-| `~"feature request"` | multi-word label by name |
-| `9ba12248` | specific commit |
-| `9ba12248...b19a04f5` | commit range comparison |
-| `[README](doc/README)` | repository file references |
+| input | references |
+|:-----------------------|:--------------------------- |
+| `@user_name` | specific user |
+| `@group_name` | specific group |
+| `@all` | entire team |
+| `#123` | issue |
+| `!123` | merge request |
+| `$123` | snippet |
+| `~123` | label by ID |
+| `~bug` | one-word label by name |
+| `~"feature request"` | multi-word label by name |
+| `%123` | milestone by ID |
+| `%v1.23` | one-word milestone by name |
+| `%"release candidate"` | multi-word milestone by name |
+| `9ba12248` | specific commit |
+| `9ba12248...b19a04f5` | commit range comparison |
+| `[README](doc/README)` | repository file references |
GFM also recognizes certain cross-project references:
@@ -206,6 +223,7 @@ GFM also recognizes certain cross-project references:
|:----------------------------------------|:------------------------|
| `namespace/project#123` | issue |
| `namespace/project!123` | merge request |
+| `namespace/project%123` | milestone |
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
@@ -402,7 +420,7 @@ There are two ways to create links, inline-style and reference-style.
[I'm a reference-style link][Arbitrary case-insensitive reference text]
-[I'm a relative reference to a repository file](LICENSE)
+[I'm a relative reference to a repository file](LICENSE)[^1]
[You can use numbers for reference-style link definitions][1]
@@ -594,3 +612,4 @@ By including colons in the header row, you can align the text within that column
[rouge]: http://rouge.jneen.net/ "Rouge website"
[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
+[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 5ec0a2069b5..8f9ef054949 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -355,7 +355,7 @@ sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
```
#### Problems when importing CI database to GitLab
-If you were migrating CI database from MySQL to PostgreSQL manually you can see errros during import about missing sequences:
+If you were migrating CI database from MySQL to PostgreSQL manually you can see errors during import about missing sequences:
```
ALTER SEQUENCE
ERROR: relation "ci_builds_id_seq" does not exist
diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md
new file mode 100644
index 00000000000..0d17799372f
--- /dev/null
+++ b/doc/monitoring/health_check.md
@@ -0,0 +1,66 @@
+# Health Check
+
+>**Note:** This feature was [introduced][ce-3888] in GitLab 8.8.
+
+GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
+endpoint. The health check reports on the overall system status based on the status of
+the database connection, the state of the database migrations, and the ability to write
+and access the cache. This endpoint can be provided to uptime monitoring services like
+[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health].
+
+## Access Token
+
+An access token needs to be provided while accessing the health check endpoint. The current
+accepted token can be found on the `admin/health_check` page of your GitLab instance.
+
+![access token](img/health_check_token.png)
+
+The access token can be passed as a URL parameter:
+
+```
+https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN
+```
+
+or as an HTTP header:
+
+```bash
+curl -H "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
+```
+
+## Using the Endpoint
+
+Once you have the access token, health information can be retrieved as plain text, JSON,
+or XML using the `health_check` endpoint:
+
+- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN`
+- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN`
+- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN`
+
+You can also ask for the status of specific services:
+
+- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN`
+- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN`
+- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN`
+
+For example, the JSON output of the following health check:
+
+```bash
+curl -H "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
+```
+
+would be like:
+
+```
+{"healthy":true,"message":"success"}
+```
+
+## Status
+
+On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
+will return a valid successful HTTP status code, and a `success` message. Ideally your
+uptime monitoring should look for the success message.
+
+[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
+[pingdom]: https://www.pingdom.com
+[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
+[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png
new file mode 100644
index 00000000000..2daf8606b00
--- /dev/null
+++ b/doc/monitoring/img/health_check_token.png
Binary files differ
diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md
index 39086b7a251..54adb99386a 100644
--- a/doc/operations/moving_repositories.md
+++ b/doc/operations/moving_repositories.md
@@ -134,7 +134,7 @@ sudo -u git sh -c '
cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\
/usr/bin/env JOBS=10 \
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
- /var/opt/gitlab/transfer-logs/succes-$(date +%s).log \
+ /var/opt/gitlab/transfer-logs/success-$(date +%s).log \
/var/opt/gitlab/git-data/repositories \
/mnt/gitlab/repositories
'
@@ -145,7 +145,7 @@ sudo -u git -H sh -c '
cat /home/git/transfer-logs/* | sort | uniq -u |\
/usr/bin/env JOBS=10 \
bin/parallel-rsync-repos \
- /home/git/transfer-logs/succes-$(date +%s).log \
+ /home/git/transfer-logs/success-$(date +%s).log \
/home/git/repositories \
/mnt/gitlab/repositories
`
@@ -164,7 +164,7 @@ sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
sudo -u git \
/usr/bin/env JOBS=10 \
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
- succes-$(date +%s).log \
+ success-$(date +%s).log \
/var/opt/gitlab/git-data/repositories \
/mnt/gitlab/repositories
@@ -174,7 +174,7 @@ sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
sudo -u git -H \
/usr/bin/env JOBS=10 \
bin/parallel-rsync-repos \
- succes-$(date +%s).log \
+ success-$(date +%s).log \
/home/git/repositories \
/mnt/gitlab/repositories
```
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 6219693b8a8..b76ce31cbad 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -27,6 +27,7 @@ documentation](../workflow/add-user/add-user.md).
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
| Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
@@ -37,6 +38,8 @@ documentation](../workflow/add-user/add-user.md).
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
+| Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 6be954ad68b..a49c43b8ef2 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -8,4 +8,4 @@
- [User management](user_management.md)
- [Webhooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk
-- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
+- [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
diff --git a/doc/security/README.md b/doc/security/README.md
index 4cd0fdd4094..38706e48ec5 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -8,3 +8,4 @@
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md)
+- [Send email confirmation on sign-up](user_email_confirmation.md)
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
new file mode 100644
index 00000000000..4293944ae8b
--- /dev/null
+++ b/doc/security/user_email_confirmation.md
@@ -0,0 +1,7 @@
+# User email confirmation at sign-up
+
+Gitlab admin can enable email confirmation on sign-up, if you want to confirm all
+user emails before they are able to sign-in.
+
+In the Admin area under **Settings** (`/admin/application_settings`), go to section
+**Sign-in Restrictions** and look for **Send confirmation email on sign-up** option.
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index 4a2c6ea91d2..bb463d43a7c 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -86,6 +86,14 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
### 7. Update configuration files
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-6-stable:config/gitlab.yml.example origin/8-7-stable:config/gitlab.yml.example
+```
+
#### Git configuration
Disable `git gc --auto` because GitLab runs `git gc` for us already.
diff --git a/doc/update/8.7-to-8.8.md b/doc/update/8.7-to-8.8.md
new file mode 100644
index 00000000000..32906650f6f
--- /dev/null
+++ b/doc/update/8.7-to-8.8.md
@@ -0,0 +1,162 @@
+# From 8.7 to 8.8
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-8-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v2.7.2
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.1
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-7-stable:config/gitlab.yml.example origin/8-8-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Disable `git gc --auto` because GitLab runs `git gc` for us already.
+
+```sh
+sudo -u git -H git config --global gc.auto 0
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-7-stable:lib/support/nginx/gitlab-ssl origin/8-8-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-7-stable:lib/support/nginx/gitlab origin/8-8-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/lib/support/init.d/gitlab.default.example#L37
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.7)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.6 to 8.7](8.6-to-8.7.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
index a770633c9b8..975d72164b4 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -29,7 +29,7 @@ Based on your installation, choose a section below that fits your needs.
## Omnibus Packages
-- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html)
+- The [Omnibus update guide](http://docs.gitlab.com/omnibus/update/README.html)
contains the steps needed to update an Omnibus GitLab package.
## Installation from source
@@ -86,10 +86,10 @@ possible.
information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md)
-[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html
+[omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html
[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
[ee-ce]: ../downgrade_ee_to_ce/README.md
[ce]: https://about.gitlab.com/features/#community
[ee]: https://about.gitlab.com/features/#enterprise
-[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
+[omni-ce-ee]: http://docs.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index b4283a526f3..60729316cde 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 --with postgres --deployment
+sudo -u git -H bundle install --without development test mysql --deployment
# MySQL
-sudo -u git -H bundle install --without development test postgres --with mysql --deployment
+sudo -u git -H bundle install --without development test postgres --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index c1c51302e79..8559b67af04 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -13,6 +13,19 @@ You can configure webhooks to listen for specific events like pushes, issues or
Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
+## Webhook endpoint tips
+
+If you are writing your own endpoint (web server) that will receive
+GitLab webhooks keep in mind the following things:
+
+- Your endpoint should send its HTTP response as fast as possible. If
+ you wait too long, GitLab may decide the hook failed and retry it.
+- Your endpoint should ALWAYS return a valid HTTP response. If you do
+ not do this then GitLab will think the hook failed and retry it.
+ Most HTTP libraries take care of this for you automatically but if
+ you are writing a low-level hook this is important to remember.
+- GitLab ignores the HTTP status code returned by your endpoint.
+
## SSL Verification
By default, the SSL certificate of the webhook endpoint is verified based on
@@ -682,6 +695,61 @@ X-Gitlab-Event: Merge Request Hook
}
```
+## Wiki Page events
+
+Triggered when a wiki page is created or edited.
+
+**Request Header**:
+
+```
+X-Gitlab-Event: Wiki Page Hook
+```
+
+**Request Body**:
+
+```json
+{
+ "object_kind": "wiki_page",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
+ },
+ "project": {
+ "name": "awesome-project",
+ "description": "This is awesome",
+ "web_url": "http://example.com/root/awesome-project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:root/awesome-project.git",
+ "git_http_url": "http://example.com/root/awesome-project.git",
+ "namespace": "root",
+ "visibility_level": 0,
+ "path_with_namespace": "root/awesome-project",
+ "default_branch": "master",
+ "homepage": "http://example.com/root/awesome-project",
+ "url": "git@example.com:root/awesome-project.git",
+ "ssh_url": "git@example.com:root/awesome-project.git",
+ "http_url": "http://example.com/root/awesome-project.git"
+ },
+ "wiki": {
+ "web_url": "http://example.com/root/awesome-project/wikis/home",
+ "git_ssh_url": "git@example.com:root/awesome-project.wiki.git",
+ "git_http_url": "http://example.com/root/awesome-project.wiki.git",
+ "path_with_namespace": "root/awesome-project.wiki",
+ "default_branch": "master"
+ },
+ "object_attributes": {
+ "title": "Awesome",
+ "content": "awesome content goes here",
+ "format": "markdown",
+ "message": "adding an awesome page to the wiki",
+ "slug": "awesome",
+ "url": "http://example.com/root/awesome-project/wikis/awesome",
+ "action": "create"
+ }
+}
+```
+
#### Example webhook receiver
If you want to see GitLab's webhooks in action for testing purposes you can use
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 1b354bcc0f1..2b2f140f8bf 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that
There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
If the assigned person does not feel comfortable they can close the merge request without merging.
-In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://doc.gitlab.com/ce/permissions/permissions.html).
+In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow
@@ -187,7 +187,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
-In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
+In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 52bf611dc5e..34ada1774d8 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -54,7 +54,7 @@ If necessary, you can increase the access level of an individual user for a spec
## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
-See [the GitLab Enterprise Edition documentation](http://doc.gitlab.com/ee/integration/ldap.html) for more information.
+See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information.
## Allowing only admins to create groups
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index e670e415c71..a7dfac2c120 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -44,5 +44,5 @@ case the namespace is taken, the project will be imported on the user's
namespace.
[gh-import]: ../../integration/github.md "GitHub integration"
-[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
+[ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
diff --git a/doc/workflow/importing/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index 1117db98e7e..dcc00074b75 100644
--- a/doc/workflow/importing/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
@@ -2,7 +2,7 @@
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
GitLab support is enabled on your GitLab instance.
-You can read more about GitLab support [here](http://doc.gitlab.com/ce/integration/gitlab.html)
+You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
To get to the importer page you need to go to "New project" page.
![New project page](gitlab_importer/new_project_page.png)
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 31620044b15..9fe065fa680 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -4,7 +4,7 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
-GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
(EE only), however in certain environments it is not always convenient to use
different commands to differentiate between the large files and regular ones.
@@ -127,7 +127,7 @@ To prevent this from happening, set the lfs url in project Git config:
```bash
-git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/objects/batch"
+git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
```
### Credentials are always required when pushing an object
@@ -152,4 +152,4 @@ If you are using OS X you can use `osxkeychain` to store and encrypt your creden
For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
More details about various methods of storing the user credentials can be found
-on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
+on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 80817c98d22..cbca94c0b5e 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -69,7 +69,7 @@ In all of the below cases, the notification will be sent to:
...with notification level "Participating" or higher
-- Watchers: project members with notification level "Watch"
+- Watchers: users with notification level "Watch"
- Subscribers: anyone who manually subscribed to the issue/merge request
| Event | Sent to |
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 2fd097d100b..26e67503021 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -10,14 +10,9 @@ Feature: Project Active Tab
Then the active main tab should be Home
And no other main tabs should be active
- Scenario: On Project Files
+ Scenario: On Project Code
Given I visit my project's files page
- Then the active main tab should be Files
- And no other main tabs should be active
-
- Scenario: On Project Commits
- Given I visit my project's commits page
- Then the active main tab should be Commits
+ Then the active main tab should be Code
And no other main tabs should be active
Scenario: On Project Issues
@@ -30,11 +25,6 @@ Feature: Project Active Tab
Then the active main tab should be Merge Requests
And no other main tabs should be active
- Scenario: On Project Members
- Given I visit my project's members page
- Then the active main tab should be Members
- And no other main tabs should be active
-
Scenario: On Project Wiki
Given I visit my project's wiki page
Then the active main tab should be Wiki
@@ -49,13 +39,6 @@ Feature: Project Active Tab
# Sub Tabs: Settings
- Scenario: On Project Settings/Edit
- Given I visit my project's settings page
- And I click the "Edit" tab
- Then the active sub nav should be Edit
- And no other sub navs should be active
- And the active main tab should be Settings
-
Scenario: On Project Settings/Hooks
Given I visit my project's settings page
And I click the "Hooks" tab
@@ -70,40 +53,52 @@ Feature: Project Active Tab
And no other sub navs should be active
And the active main tab should be Settings
- # Sub Tabs: Commits
+ Scenario: On Project Members
+ Given I visit my project's members page
+ Then the active sub nav should be Members
+ And no other sub navs should be active
+ And the active main tab should be Settings
+
+ # Sub Tabs: Code
+
+ Scenario: On Project Code/Files
+ Given I visit my project's files page
+ Then the active sub tab should be Files
+ And no other sub tabs should be active
+ And the active main tab should be Code
- Scenario: On Project Commits/Commits
+ Scenario: On Project Code/Commits
Given I visit my project's commits page
Then the active sub tab should be Commits
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Network
+ Scenario: On Project Code/Network
Given I visit my project's network page
Then the active sub tab should be Network
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Compare
+ Scenario: On Project Code/Compare
Given I visit my project's commits page
And I click the "Compare" tab
Then the active sub tab should be Compare
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Branches
+ Scenario: On Project Code/Branches
Given I visit my project's commits page
And I click the "Branches" tab
Then the active sub tab should be Branches
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Tags
+ Scenario: On Project Code/Tags
Given I visit my project's commits page
And I click the "Tags" tab
Then the active sub tab should be Tags
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
Scenario: On Project Issues/Browse
Given I visit my project's issues page
diff --git a/features/project/builds/summary.feature b/features/project/builds/summary.feature
index 3c029a973df..550ebccf0d7 100644
--- a/features/project/builds/summary.feature
+++ b/features/project/builds/summary.feature
@@ -24,3 +24,4 @@ Feature: Project Builds Summary
Then recent build has been erased
And recent build summary does not have artifacts widget
And recent build summary contains information saying that build has been erased
+ And the build count cache is updated
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
deleted file mode 100644
index a4be39b2d40..00000000000
--- a/features/project/commits/tags.feature
+++ /dev/null
@@ -1,46 +0,0 @@
-@project_commits
-Feature: Project Commits Tags
- Background:
- Given I sign in as a user
- And I own project "Shop"
- Given I visit project tags page
-
- Scenario: I can see all git tags
- Then I should see "Shop" all tags list
-
- Scenario: I create a tag
- And I click new tag link
- And I submit new tag form
- Then I should see new tag created
-
- Scenario: I create a tag with release notes
- Given I click new tag link
- And I submit new tag form with release notes
- Then I should see new tag created
- And I should see tag release notes
-
- Scenario: I create a tag with invalid name
- And I click new tag link
- And I submit new tag form with invalid name
- Then I should see new an error that tag is invalid
-
- Scenario: I create a tag with invalid reference
- And I click new tag link
- And I submit new tag form with invalid reference
- Then I should see new an error that tag ref is invalid
-
- Scenario: I create a tag that already exists
- And I click new tag link
- And I submit new tag form with tag that already exists
- Then I should see new an error that tag already exists
-
- Scenario: I delete a tag
- Given I visit tag 'v1.1.0' page
- Given I delete tag 'v1.1.0'
- Then I should not see tag 'v1.1.0'
-
- Scenario: I add release notes to the tag
- Given I visit tag 'v1.1.0' page
- When I click edit tag link
- And I fill release notes and submit form
- Then I should see tag release notes
diff --git a/features/project/create.feature b/features/project/create.feature
index 27136798e36..67336d73bf7 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -7,20 +7,8 @@ Feature: Project Create
@javascript
Scenario: User create a project
Given I sign in as a user
- When I visit new project page
- And I have an ssh key
- And fill project form with valid data
- Then I should see project page
- And I should see empty project instuctions
-
- @javascript
- Scenario: Empty project instructions
- Given I sign in as a user
And I have an ssh key
When I visit new project page
And fill project form with valid data
- Then I see empty project instuctions
- And I click on HTTP
- Then Remote url should update to http link
- And If I click on SSH
- Then Remote url should update to ssh link
+ Then I should see project page
+ And I should see empty project instructions
diff --git a/features/project/project.feature b/features/project/project.feature
index f1f3ed26065..aa22401c88e 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,15 +18,6 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
- Scenario: I should have back to group button
- And project "Shop" belongs to group
- And I visit project "Shop" page
- Then I should see back to group button
-
- Scenario: I should have back to group button
- And I visit project "Shop" page
- Then I should see back to dashboard button
-
Scenario: I should have readme on page
And I visit project "Shop" page
Then I should see project "Shop" README
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index 10e7c234610..c73d0b32337 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -8,19 +8,21 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to files tab
Given I press "g" and "f"
- Then the active main tab should be Files
+ Then the active main tab should be Code
+ Then the active sub tab should be Files
@javascript
Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c"
- Then the active main tab should be Commits
+ Then the active main tab should be Code
+ Then the active sub tab should be Commits
@javascript
Scenario: Navigate to network tab
Given I press "g" and "n"
Then the active sub tab should be Network
- And the active main tab should be Commits
+ And the active main tab should be Code
@javascript
Scenario: Navigate to graphs tab
diff --git a/features/steps/admin/active_tab.rb b/features/steps/admin/active_tab.rb
index 90d13abdb13..f2db1801389 100644
--- a/features/steps/admin/active_tab.rb
+++ b/features/steps/admin/active_tab.rb
@@ -1,7 +1,7 @@
class Spinach::Features::AdminActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
- include SharedActiveTab
+ include SharedSidebarActiveTab
step 'the active main tab should be Home' do
ensure_active_main_tab('Overview')
@@ -34,4 +34,12 @@ class Spinach::Features::AdminActiveTab < Spinach::FeatureSteps
step 'the active main tab should be Messages' do
ensure_active_main_tab('Messages')
end
+
+ step 'no other main tabs should be active' do
+ expect(page).to have_selector('.nav-sidebar > li.active', count: 1)
+ end
+
+ def ensure_active_main_tab(content)
+ expect(find('.nav-sidebar > li.active')).to have_content(content)
+ end
end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 4bc290b6bdf..8fb8a86d58b 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -158,7 +158,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
step 'I should not see twitter details' do
expect(page).to have_content 'Pete'
- expect(page).to_not have_content 'twitter'
+ expect(page).not_to have_content 'twitter'
end
step 'click on ssh keys tab' do
diff --git a/features/steps/dashboard/active_tab.rb b/features/steps/dashboard/active_tab.rb
index 0e2c04fb299..04fe96cef22 100644
--- a/features/steps/dashboard/active_tab.rb
+++ b/features/steps/dashboard/active_tab.rb
@@ -1,9 +1,5 @@
class Spinach::Features::DashboardActiveTab < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
- include SharedActiveTab
-
- step 'the active main tab should be Help' do
- ensure_active_main_tab('Help')
- end
+ include SharedSidebarActiveTab
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index b5980b35102..80ed4c6d64c 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I should see "Shop" project CI status' do
- expect(page).to have_link "Build skipped"
+ expect(page).to have_link "Commit: skipped"
end
step 'I should see last push widget' do
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index e21af72a777..8706f0e8e78 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -74,7 +74,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
def project
@project ||= begin
- project =create :project
+ project = create :project
project.team << [current_user, :master]
project
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index a2adc87f8ef..06db36c7014 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -100,7 +100,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def project
@project ||= begin
- project =create :project
+ project = create :project
project.team << [current_user, :master]
project
end
diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb
index a9083850b52..118d27888df 100644
--- a/features/steps/dashboard/shortcuts.rb
+++ b/features/steps/dashboard/shortcuts.rb
@@ -2,5 +2,6 @@ class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
- include SharedActiveTab
+ include SharedSidebarActiveTab
+ include SharedShortcuts
end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 2b23df6764b..bd8a270202e 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -20,7 +20,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
step 'I have todos' do
create(:todo, user: current_user, project: project, author: mary_jane, target: issue, action: Todo::MENTIONED)
create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::ASSIGNED)
- note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?")
+ note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?", project: project)
create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::MENTIONED, note: note)
create(:todo, user: current_user, project: project, author: john_doe, target: merge_request, action: Todo::ASSIGNED)
end
@@ -106,7 +106,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
if pending
expect(page).to have_link 'Done'
else
- expect(page).to_not have_link 'Done'
+ expect(page).not_to have_link 'Done'
end
end
end
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 3b59089a093..4724a326277 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -22,8 +22,4 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
step 'the active main tab should be Audit Log' do
ensure_active_main_tab('Audit Log')
end
-
- def ensure_active_main_tab(content)
- expect(find('.layout-nav li.active')).to have_content(content)
- end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 909de31a479..b1a87b96efd 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -166,7 +166,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I have group with projects' do
- @group = create(:group)
+ @group = create(:group)
@group.add_owner(current_user)
@project = create(:project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 19d81453d8c..745fd3471c4 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -16,12 +16,14 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
end
step 'I click the "Snippets" tab' do
- click_link('Snippets')
+ page.within('.layout-nav') do
+ click_link('Snippets')
+ end
end
- step 'I click the "Edit" tab' do
- page.within '.sidebar-subnav' do
- click_link('Project Settings')
+ step 'I click the "Edit Project"' do
+ page.within '.layout-nav .controls' do
+ click_link('Edit Project')
end
end
@@ -33,14 +35,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Deploy Keys')
end
- step 'the active sub nav should be Team' do
+ step 'the active sub nav should be Members' do
ensure_active_sub_nav('Members')
end
- step 'the active sub nav should be Edit' do
- ensure_active_sub_nav('Project')
- end
-
step 'the active sub nav should be Hooks' do
ensure_active_sub_nav('Webhooks')
end
@@ -56,17 +54,15 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
end
step 'I click the "Branches" tab' do
- click_link('Branches')
+ page.within '.content' do
+ click_link('Branches')
+ end
end
step 'I click the "Tags" tab' do
click_link('Tags')
end
- step 'the active sub tab should be Commits' do
- ensure_active_sub_tab('Commits')
- end
-
step 'the active sub tab should be Compare' do
ensure_active_sub_tab('Compare')
end
@@ -82,11 +78,15 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues
step 'I click the "Milestones" tab' do
- click_link('Milestones')
+ page.within('.layout-nav') do
+ click_link('Milestones')
+ end
end
step 'I click the "Labels" tab' do
- click_link('Labels')
+ page.within('.layout-nav') do
+ click_link('Labels')
+ end
end
step 'the active sub tab should be Issues' do
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index e9e2359146e..374eb0b0e07 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -36,4 +36,8 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
expect(page).to have_content 'Build has been erased'
end
end
+
+ step 'the build count cache is updated' do
+ expect(@build.project.running_or_pending_build_count).to eq @build.project.builds.running_or_pending.count(:all)
+ end
end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 93c37bf507f..e1b29f1e57a 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -105,7 +105,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I should not see button to create a new merge request' do
- expect(page).to_not have_link 'Create Merge Request'
+ expect(page).not_to have_link 'Create Merge Request'
end
step 'I should see button to the merge request' do
@@ -173,7 +173,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I see commit ci info' do
- expect(page).to have_content "build: pending"
+ expect(page).to have_content "Builds for 1 pipeline pending"
end
step 'I click status link' do
@@ -181,7 +181,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I see builds list' do
- expect(page).to have_content "build: pending"
+ expect(page).to have_content "Builds for 1 pipeline pending"
expect(page).to have_content "1 build"
end
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
deleted file mode 100644
index 912ba580efd..00000000000
--- a/features/steps/project/commits/tags.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'I should see "Shop" all tags list' do
- expect(page).to have_content "Tags"
- expect(page).to have_content "v1.0.0"
- end
-
- step 'I click new tag link' do
- click_link 'New tag'
- end
-
- step 'I submit new tag form' do
- fill_in 'tag_name', with: 'v7.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with release notes' do
- fill_in 'tag_name', with: 'v7.0'
- fill_in 'ref', with: 'master'
- fill_in 'release_description', with: 'Awesome release notes'
- click_button 'Create tag'
- end
-
- step 'I fill release notes and submit form' do
- fill_in 'release_description', with: 'Awesome release notes'
- click_button 'Save changes'
- end
-
- step 'I submit new tag form with invalid name' do
- fill_in 'tag_name', with: 'v 1.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with invalid reference' do
- fill_in 'tag_name', with: 'foo'
- fill_in 'ref', with: 'foo'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with tag that already exists' do
- fill_in 'tag_name', with: 'v1.0.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I should see new tag created' do
- expect(page).to have_content 'v7.0'
- end
-
- step 'I should see new an error that tag is invalid' do
- expect(page).to have_content 'Tag name invalid'
- end
-
- step 'I should see new an error that tag ref is invalid' do
- 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 v1.0.0 already exists'
- end
-
- step "I visit tag 'v1.1.0' page" do
- click_link 'v1.1.0'
- end
-
- step "I delete tag 'v1.1.0'" do
- page.within('.content') do
- first('.btn-remove').click
- end
- end
-
- step "I should not see tag 'v1.1.0'" do
- page.within '.tags' do
- expect(page).not_to have_link 'v1.1.0'
- end
- end
-
- step 'I click edit tag link' do
- click_link 'Edit release notes'
- end
-
- step 'I should see tag release notes' do
- expect(page).to have_content 'Awesome release notes'
- end
-end
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 422b151eaa2..5f5f806df36 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -13,33 +13,9 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
end
- step 'I should see empty project instuctions' do
+ step 'I should see empty project instructions' do
expect(page).to have_content "git init"
expect(page).to have_content "git remote"
expect(page).to have_content Project.last.url_to_repo
end
-
- step 'I see empty project instuctions' do
- expect(page).to have_content "git init"
- expect(page).to have_content "git remote"
- expect(page).to have_content Project.last.url_to_repo
- end
-
- step 'I click on HTTP' do
- find('#clone-dropdown').click
- find('.http-selector').click
- end
-
- step 'Remote url should update to http link' do
- expect(page).to have_content "git remote add origin #{Project.last.http_url_to_repo}"
- end
-
- step 'If I click on SSH' do
- find('#clone-dropdown').click
- find('.ssh-selector').click
- end
-
- step 'Remote url should update to ssh link' do
- expect(page).to have_content "git remote add origin #{Project.last.url_to_repo}"
- end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 527f7853da9..8abeb5ee242 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I goto the Merge Requests page' do
- page.within '.page-sidebar-expanded' do
+ page.within '.layout-nav' do
click_link "Merge Requests"
end
end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index b1ffe7f7b4c..13c0713669a 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'hook should be triggered' do
expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
expect(page).to have_selector '.flash-notice',
- text: 'Hook successfully executed.'
+ text: 'Hook executed successfully: HTTP 200'
end
step 'I should see hook error message' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index c5d45709b44..1b14659b4df 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -39,8 +39,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
step 'I can see the activity and food categories' do
page.within '.emoji-menu' do
- expect(page).to_not have_selector 'Activity'
- expect(page).to_not have_selector 'Food'
+ expect(page).not_to have_selector 'Activity'
+ expect(page).not_to have_selector 'Food'
end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index fc12843ea5c..5cd431e05d5 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -216,7 +216,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
page.within 'li.issue:nth-child(3)' do
expect(page).to have_content 'Bugfix'
- expect(page).to_not have_content '0 0'
+ expect(page).not_to have_content '0 0'
end
end
end
@@ -235,7 +235,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
page.within 'li.issue:nth-child(3)' do
expect(page).to have_content 'Bugfix'
- expect(page).to_not have_content '0 0'
+ expect(page).not_to have_content '0 0'
end
end
end
@@ -348,7 +348,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'another user adds a comment with text "Yay!" to issue "Release 0.4"' do
issue = Issue.find_by!(title: 'Release 0.4')
- create(:note_on_issue, noteable: issue, note: 'Yay!')
+ create(:note_on_issue, noteable: issue, project: project, note: 'Yay!')
end
step 'I should see a new comment with text "Yay!"' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 0ca2d6257c3..8d87f6a7a58 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -24,8 +24,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I should see labels help message' do
page.within '.labels' do
- expect(page).to have_content 'Create first label or generate default set of '\
- 'labels'
+ expect(page).to have_content 'Create a label or generate a default set '\
+ 'of labels'
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 3b1a00f628a..b30346790eb 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -203,7 +203,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.within 'li.merge-request:nth-child(3)' do
expect(page).to have_content 'Bug NS-05'
- expect(page).to_not have_content '0 0'
+ expect(page).not_to have_content '0 0'
end
end
end
@@ -222,7 +222,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.within 'li.merge-request:nth-child(3)' do
expect(page).to have_content 'Bug NS-05'
- expect(page).to_not have_content '0 0'
+ expect(page).not_to have_content '0 0'
end
end
end
@@ -273,7 +273,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do
mr = MergeRequest.find_by(title: "Bug NS-05")
create(:note_on_merge_request_diff, project: project,
- noteable_id: mr.id,
+ noteable: mr,
author: user_exists("John Doe"),
line_code: sample_commit.line_code,
note: 'Line is wrong')
@@ -525,7 +525,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see merge request "Bug NS-05" with CI status' do
page.within ".mr-list" do
- expect(page).to have_link "Build pending"
+ expect(page).to have_link "Pipeline: pending"
end
end
@@ -567,7 +567,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_diff_line(sample_compare.changes[1][:line_code])
end
- def have_visible_content (text)
+ def have_visible_content(text)
have_css("*", text: text, visible: true)
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index ef185861e00..a1785311c2b 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -114,7 +114,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should not see "Snippets" button' do
- expect(page).not_to have_link 'Snippets'
+ page.within '.content' do
+ expect(page).not_to have_link 'Snippets'
+ end
end
step 'project "Shop" belongs to group' do
@@ -123,14 +125,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
@project.save!
end
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
- step 'I should see back to group button' do
- expect(page).to have_content 'Go to group'
- end
-
step 'I click notifications drop down button' do
click_link 'notifications-button'
end
diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb
index 8c1d09d6cc6..47de4b91df1 100644
--- a/features/steps/project/project_find_file.rb
+++ b/features/steps/project/project_find_file.rb
@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
end
step 'I should see "find file" page' do
- ensure_active_main_tab('Files')
+ ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
step 'I fill in Find by path with "git"' do
- ensure_active_main_tab('Files')
+ ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
diff --git a/features/steps/project/project_milestone.rb b/features/steps/project/project_milestone.rb
index 2508c09e36d..1864b3a2b52 100644
--- a/features/steps/project/project_milestone.rb
+++ b/features/steps/project/project_milestone.rb
@@ -52,7 +52,7 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
end
step 'I click link "Labels"' do
- page.within('.nav-links') do
+ page.within('.layout-nav .nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb
index 49e9c5520bb..8143b01ca40 100644
--- a/features/steps/project/project_shortcuts.rb
+++ b/features/steps/project/project_shortcuts.rb
@@ -3,6 +3,7 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedProjectTab
+ include SharedShortcuts
step 'I press "g" and "f"' do
find('body').native.send_key('g')
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 786a0cad975..beb8ecfc799 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -43,12 +43,12 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
step 'I click link "Edit"' do
page.within ".detail-page-header" do
- click_link "Edit"
+ first(:link, "Edit").click
end
end
step 'I click link "Delete"' do
- click_link "Delete"
+ first(:link, "Delete").click
end
step 'I submit new snippet "Snippet three"' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index c26d7a15212..2c0498de3b9 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -337,13 +337,15 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see buttons for allowed commands' do
- expect(page).to have_content 'Raw'
- expect(page).to have_content 'History'
- expect(page).to have_content 'Permalink'
- expect(page).not_to have_content 'Edit'
- expect(page).not_to have_content 'Blame'
- expect(page).to have_content 'Delete'
- expect(page).to have_content 'Replace'
+ page.within '.content' do
+ expect(page).to have_content 'Raw'
+ expect(page).to have_content 'History'
+ expect(page).to have_content 'Permalink'
+ expect(page).not_to have_content 'Edit'
+ expect(page).not_to have_content 'Blame'
+ expect(page).to have_content 'Delete'
+ expect(page).to have_content 'Replace'
+ end
end
step 'I should see a notice about a new fork having been created' do
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index 3fbcf770b62..c6ced747370 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -126,7 +126,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
step 'I share project with group "OpenSource"' do
project = Project.find_by(name: 'Shop')
- os_group = create(:group, name: 'OpenSource')
+ os_group = create(:group, name: 'OpenSource')
create(:project, group: os_group)
@os_user1 = create(:user)
@os_user2 = create(:user)
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index 0bee91d758d..ace717b9909 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -2,7 +2,7 @@ module SharedActiveTab
include Spinach::DSL
def ensure_active_main_tab(content)
- expect(find('.nav-sidebar > li.active')).to have_content(content)
+ expect(find('.layout-nav li.active')).to have_content(content)
end
def ensure_active_sub_tab(content)
@@ -10,11 +10,11 @@ module SharedActiveTab
end
def ensure_active_sub_nav(content)
- expect(find('.sidebar-subnav > li.active')).to have_content(content)
+ expect(find('.layout-nav .controls li.active')).to have_content(content)
end
step 'no other main tabs should be active' do
- expect(page).to have_selector('.nav-sidebar > li.active', count: 1)
+ expect(page).to have_selector('.layout-nav .nav-links > li.active', count: 1)
end
step 'no other sub tabs should be active' do
@@ -22,26 +22,6 @@ module SharedActiveTab
end
step 'no other sub navs should be active' do
- expect(page).to have_selector('.sidebar-subnav > li.active', count: 1)
- end
-
- step 'the active main tab should be Home' do
- ensure_active_main_tab('Projects')
- end
-
- step 'the active main tab should be Projects' do
- ensure_active_main_tab('Projects')
- end
-
- step 'the active main tab should be Issues' do
- ensure_active_main_tab('Issues')
- end
-
- step 'the active main tab should be Merge Requests' do
- ensure_active_main_tab('Merge Requests')
- end
-
- step 'the active main tab should be Help' do
- ensure_active_main_tab('Help')
+ expect(page).to have_selector('.layout-nav .controls li.active', count: 1)
end
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index e846c52d474..e8b1e4b4879 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click")
end
@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click")
end
@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click
end
@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code)
- page.within("form[id$='#{sample_commit.del_line_code}']") do
+ page.within("form[id$='#{sample_commit.del_line_code}-true']") do
fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click
end
@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}-true']") do
fill_in 'note[note]', with: ':smile:'
click_button('Comment')
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index a58b3cb7e16..733e80b7279 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -111,7 +111,7 @@ module SharedIssuable
step 'I sort the list by "Oldest updated"' do
find('button.dropdown-toggle.btn').click
- page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
click_link "Oldest updated"
end
end
@@ -119,7 +119,7 @@ module SharedIssuable
step 'I sort the list by "Least popular"' do
find('button.dropdown-toggle.btn').click
- page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
click_link 'Least popular'
end
end
@@ -127,13 +127,13 @@ module SharedIssuable
step 'I sort the list by "Most popular"' do
find('button.dropdown-toggle.btn').click
- page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
click_link 'Most popular'
end
end
step 'The list should be sorted by "Oldest updated"' do
- page.within('div.dropdown.inline.prepend-left-10') do
+ page.within('.content div.dropdown.inline.prepend-left-10') do
expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
end
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index a3c3887ab46..3d7c6ef9d2d 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -107,7 +107,7 @@ module SharedNote
end
step 'I should see no notes at all' do
- expect(page).to_not have_css('.note')
+ expect(page).not_to have_css('.note')
end
# Markdown
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index ea5f9580308..ce9ea7ee18a 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -95,7 +95,7 @@ module SharedProject
step 'I should see project settings' do
expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
expect(page).to have_content("Project name")
- expect(page).to have_content("Features:")
+ expect(page).to have_content("Features")
end
def current_project
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 4fc2ece79ff..bfee8793301 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -8,12 +8,8 @@ module SharedProjectTab
ensure_active_main_tab('Project')
end
- step 'the active main tab should be Files' do
- ensure_active_main_tab('Files')
- end
-
- step 'the active main tab should be Commits' do
- ensure_active_main_tab('Commits')
+ step 'the active main tab should be Code' do
+ ensure_active_main_tab('Code')
end
step 'the active main tab should be Graphs' do
@@ -41,9 +37,7 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- page.within '.nav-sidebar' do
- expect(page).to have_content('Go to project')
- end
+ expect(page).to have_selector('.layout-nav .nav-links > li.active', count: 0)
end
step 'the active main tab should be Activity' do
@@ -53,4 +47,12 @@ module SharedProjectTab
step 'the active sub tab should be Network' do
ensure_active_sub_tab('Network')
end
+
+ step 'the active sub tab should be Files' do
+ ensure_active_sub_tab('Files')
+ end
+
+ step 'the active sub tab should be Commits' do
+ ensure_active_sub_tab('Commits')
+ end
end
diff --git a/features/steps/shared/shortcuts.rb b/features/steps/shared/shortcuts.rb
index bbb7afec0ad..a75a8474d26 100644
--- a/features/steps/shared/shortcuts.rb
+++ b/features/steps/shared/shortcuts.rb
@@ -1,4 +1,4 @@
-module SharedActiveTab
+module SharedShortcuts
include Spinach::DSL
step 'I press "g" and "p"' do
diff --git a/features/steps/shared/sidebar_active_tab.rb b/features/steps/shared/sidebar_active_tab.rb
new file mode 100644
index 00000000000..5c47238777f
--- /dev/null
+++ b/features/steps/shared/sidebar_active_tab.rb
@@ -0,0 +1,35 @@
+module SharedSidebarActiveTab
+ include Spinach::DSL
+
+ step 'the active main tab should be Help' do
+ ensure_active_main_tab('Help')
+ end
+
+ step 'no other main tabs should be active' do
+ expect(page).to have_selector('.nav-sidebar > li.active', count: 1)
+ end
+
+ def ensure_active_main_tab(content)
+ expect(find('.nav-sidebar li.active')).to have_content(content)
+ end
+
+ step 'the active main tab should be Home' do
+ ensure_active_main_tab('Projects')
+ end
+
+ step 'the active main tab should be Projects' do
+ ensure_active_main_tab('Projects')
+ end
+
+ step 'the active main tab should be Issues' do
+ ensure_active_main_tab('Issues')
+ end
+
+ step 'the active main tab should be Merge Requests' do
+ ensure_active_main_tab('Merge Requests')
+ end
+
+ step 'the active main tab should be Help' do
+ ensure_active_main_tab('Help')
+ end
+end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 023032e679f..19366b11071 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -14,12 +14,12 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
step 'I click link "Edit"' do
page.within ".detail-page-header" do
- click_link "Edit"
+ first(:link, "Edit").click
end
end
step 'I click link "Delete"' do
- click_link "Delete"
+ first(:link, "Delete").click
end
step 'I submit new snippet "Personal snippet three"' do
diff --git a/features/steps/user.rb b/features/steps/user.rb
index b1d088f07f9..59385a6ab59 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -34,7 +34,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
end
step 'I should see contributions calendar' do
- expect(page).to have_css('.cal-heatmap-container')
+ expect(page).to have_css('.js-contrib-calendar')
end
def contributed_project
diff --git a/generator_templates/active_record/migration/create_table_migration.rb b/generator_templates/active_record/migration/create_table_migration.rb
new file mode 100644
index 00000000000..27acc75dcc4
--- /dev/null
+++ b/generator_templates/active_record/migration/create_table_migration.rb
@@ -0,0 +1,35 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class <%= migration_class_name %> < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ create_table :<%= table_name %> do |t|
+<% attributes.each do |attribute| -%>
+<% if attribute.password_digest? -%>
+ t.string :password_digest<%= attribute.inject_options %>
+<% else -%>
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
+<% end -%>
+<% end -%>
+<% if options[:timestamps] %>
+ t.timestamps null: false
+<% end -%>
+ end
+<% attributes_with_index.each do |attribute| -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+<% end -%>
+ end
+end
diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb
new file mode 100644
index 00000000000..06bdea11367
--- /dev/null
+++ b/generator_templates/active_record/migration/migration.rb
@@ -0,0 +1,55 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class <%= migration_class_name %> < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+<%- if migration_action == 'add' -%>
+ def change
+<% attributes.each do |attribute| -%>
+ <%- if attribute.reference? -%>
+ add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
+ <%- else -%>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- if attribute.has_index? -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
+ <%- end -%>
+<%- end -%>
+ end
+<%- elsif migration_action == 'join' -%>
+ def change
+ create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t|
+ <%- attributes.each do |attribute| -%>
+ <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
+ end
+ end
+<%- else -%>
+ def change
+<% attributes.each do |attribute| -%>
+<%- if migration_action -%>
+ <%- if attribute.reference? -%>
+ remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
+ <%- else -%>
+ <%- if attribute.has_index? -%>
+ remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
+ remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- end -%>
+<%- end -%>
+<%- end -%>
+ end
+<%- end -%>
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index cc1004f8005..6cd909f6115 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -1,5 +1,3 @@
-Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
-
module API
class API < Grape::API
include APIGuard
@@ -25,38 +23,41 @@ module API
format :json
content_type :txt, "text/plain"
- helpers Helpers
-
- mount Groups
- mount GroupMembers
- mount Users
- mount Projects
- mount Repositories
- mount Issues
- mount Milestones
- mount Session
- mount MergeRequests
- mount Notes
- mount Internal
- mount SystemHooks
- mount ProjectSnippets
- mount ProjectMembers
- mount DeployKeys
- mount ProjectHooks
- mount Services
- mount Files
- mount Commits
- mount CommitStatus
- mount Namespaces
- mount Branches
- mount Labels
- mount Settings
- mount Keys
- mount Tags
- mount Triggers
- mount Builds
- mount Variables
- mount Runners
- mount Licenses
+ # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
+ helpers ::API::Helpers
+
+ mount ::API::Groups
+ mount ::API::GroupMembers
+ mount ::API::Users
+ mount ::API::Projects
+ mount ::API::Repositories
+ mount ::API::Issues
+ mount ::API::Milestones
+ mount ::API::Session
+ mount ::API::MergeRequests
+ mount ::API::Notes
+ mount ::API::Internal
+ mount ::API::SystemHooks
+ mount ::API::ProjectSnippets
+ mount ::API::ProjectMembers
+ mount ::API::DeployKeys
+ mount ::API::ProjectHooks
+ mount ::API::Services
+ mount ::API::Files
+ mount ::API::Commits
+ mount ::API::CommitStatuses
+ mount ::API::Namespaces
+ mount ::API::Branches
+ mount ::API::Labels
+ mount ::API::Settings
+ mount ::API::Keys
+ mount ::API::Tags
+ mount ::API::Triggers
+ mount ::API::Builds
+ mount ::API::Variables
+ mount ::API::Runners
+ mount ::API::Licenses
+ mount ::API::Subscriptions
+ mount ::API::Gitignores
end
end
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index b9994fcefda..7e67edb203a 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -2,171 +2,175 @@
require 'rack/oauth2'
-module APIGuard
- extend ActiveSupport::Concern
+module API
+ module APIGuard
+ extend ActiveSupport::Concern
- included do |base|
- # OAuth2 Resource Server Authentication
- use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
- # The authenticator only fetches the raw token string
+ included do |base|
+ # OAuth2 Resource Server Authentication
+ use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
+ # The authenticator only fetches the raw token string
- # Must yield access token to store it in the env
- request.access_token
- end
+ # Must yield access token to store it in the env
+ request.access_token
+ end
- helpers HelperMethods
+ helpers HelperMethods
- install_error_responders(base)
- end
+ install_error_responders(base)
+ end
- # Helper Methods for Grape Endpoint
- module HelperMethods
- # Invokes the doorkeeper guard.
- #
- # If token is presented and valid, then it sets @current_user.
- #
- # If the token does not have sufficient scopes to cover the requred scopes,
- # then it raises InsufficientScopeError.
- #
- # If the token is expired, then it raises ExpiredError.
- #
- # If the token is revoked, then it raises RevokedError.
- #
- # If the token is not found (nil), then it raises TokenNotFoundError.
- #
- # Arguments:
- #
- # scopes: (optional) scopes required for this guard.
- # Defaults to empty array.
- #
- def doorkeeper_guard!(scopes: [])
- if (access_token = find_access_token).nil?
- raise TokenNotFoundError
-
- else
- case validate_access_token(access_token, scopes)
- when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
- raise InsufficientScopeError.new(scopes)
- when Oauth2::AccessTokenValidationService::EXPIRED
- raise ExpiredError
- when Oauth2::AccessTokenValidationService::REVOKED
- raise RevokedError
- when Oauth2::AccessTokenValidationService::VALID
- @current_user = User.find(access_token.resource_owner_id)
+ # Helper Methods for Grape Endpoint
+ module HelperMethods
+ # Invokes the doorkeeper guard.
+ #
+ # If token is presented and valid, then it sets @current_user.
+ #
+ # If the token does not have sufficient scopes to cover the requred scopes,
+ # then it raises InsufficientScopeError.
+ #
+ # If the token is expired, then it raises ExpiredError.
+ #
+ # If the token is revoked, then it raises RevokedError.
+ #
+ # If the token is not found (nil), then it raises TokenNotFoundError.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def doorkeeper_guard!(scopes: [])
+ if (access_token = find_access_token).nil?
+ raise TokenNotFoundError
+
+ else
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
end
end
- end
- def doorkeeper_guard(scopes: [])
- if access_token = find_access_token
- case validate_access_token(access_token, scopes)
- when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
- raise InsufficientScopeError.new(scopes)
+ def doorkeeper_guard(scopes: [])
+ if access_token = find_access_token
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
- when Oauth2::AccessTokenValidationService::EXPIRED
- raise ExpiredError
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
- when Oauth2::AccessTokenValidationService::REVOKED
- raise RevokedError
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
- when Oauth2::AccessTokenValidationService::VALID
- @current_user = User.find(access_token.resource_owner_id)
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
end
end
- end
- def current_user
- @current_user
- end
+ def current_user
+ @current_user
+ end
- private
- def find_access_token
- @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
- end
+ private
- def doorkeeper_request
- @doorkeeper_request ||= ActionDispatch::Request.new(env)
- end
+ def find_access_token
+ @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
+ end
- def validate_access_token(access_token, scopes)
- Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
- end
- end
+ def doorkeeper_request
+ @doorkeeper_request ||= ActionDispatch::Request.new(env)
+ end
- module ClassMethods
- # Installs the doorkeeper guard on the whole Grape API endpoint.
- #
- # Arguments:
- #
- # scopes: (optional) scopes required for this guard.
- # Defaults to empty array.
- #
- def guard_all!(scopes: [])
- before do
- guard! scopes: scopes
+ def validate_access_token(access_token, scopes)
+ Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
end
end
- private
- def install_error_responders(base)
- error_classes = [ MissingTokenError, TokenNotFoundError,
- ExpiredError, RevokedError, InsufficientScopeError]
+ module ClassMethods
+ # Installs the doorkeeper guard on the whole Grape API endpoint.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def guard_all!(scopes: [])
+ before do
+ guard! scopes: scopes
+ end
+ end
- base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
- end
+ private
- def oauth2_bearer_token_error_handler
- Proc.new do |e|
- response =
- case e
- when MissingTokenError
- Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
-
- when TokenNotFoundError
- Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
- :invalid_token,
- "Bad Access Token.")
-
- when ExpiredError
- Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
- :invalid_token,
- "Token is expired. You can either do re-authorization or token refresh.")
-
- when RevokedError
- Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
- :invalid_token,
- "Token was revoked. You have to re-authorize from the user.")
-
- when InsufficientScopeError
- # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
- # does not include WWW-Authenticate header, which breaks the standard.
- Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
- :insufficient_scope,
- Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
- { scope: e.scopes })
- end
+ def install_error_responders(base)
+ error_classes = [ MissingTokenError, TokenNotFoundError,
+ ExpiredError, RevokedError, InsufficientScopeError]
- response.finish
+ base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ end
+
+ def oauth2_bearer_token_error_handler
+ Proc.new do |e|
+ response =
+ case e
+ when MissingTokenError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
+
+ when TokenNotFoundError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Bad Access Token.")
+
+ when ExpiredError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token is expired. You can either do re-authorization or token refresh.")
+
+ when RevokedError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token was revoked. You have to re-authorize from the user.")
+
+ when InsufficientScopeError
+ # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
+ # does not include WWW-Authenticate header, which breaks the standard.
+ Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
+ :insufficient_scope,
+ Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
+ { scope: e.scopes })
+ end
+
+ response.finish
+ end
end
end
- end
- #
- # Exceptions
- #
+ #
+ # Exceptions
+ #
- class MissingTokenError < StandardError; end
+ class MissingTokenError < StandardError; end
- class TokenNotFoundError < StandardError; end
+ class TokenNotFoundError < StandardError; end
- class ExpiredError < StandardError; end
+ class ExpiredError < StandardError; end
- class RevokedError < StandardError; end
+ class RevokedError < StandardError; end
- class InsufficientScopeError < StandardError
- attr_reader :scopes
- def initialize(scopes)
- @scopes = scopes
+ class InsufficientScopeError < StandardError
+ attr_reader :scopes
+ def initialize(scopes)
+ @scopes = scopes
+ end
end
end
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 7388ed2f4ea..9bcd33ff19e 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -2,7 +2,7 @@ require 'mime/types'
module API
# Project commit statuses API
- class CommitStatus < Grape::API
+ class CommitStatuses < Grape::API
resource :projects do
before { authenticate! }
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 93a3a5ce089..4a11c8e3620 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -107,6 +107,8 @@ module API
break if opts[:line_code]
end
+
+ opts[:type] = LegacyDiffNote.name if opts[:line_code]
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 716ca6f7ed9..790a1869f73 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -66,7 +66,8 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at
+ expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled
+ expose :created_at, :last_activity_at
expose :shared_runners_enabled
expose :creator_id
expose :namespace
@@ -170,10 +171,10 @@ module API
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
-
expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user])
end
+ expose :user_notes_count
end
class MergeRequest < ProjectEntity
@@ -187,10 +188,10 @@ module API
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
expose :merge_status
-
expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user])
end
+ expose :user_notes_count
end
class MergeRequestChanges < MergeRequest
@@ -227,9 +228,9 @@ module API
class CommitNote < Grape::Entity
expose :note
- expose(:path) { |note| note.diff_file_name }
- expose(:line) { |note| note.diff_new_line }
- expose(:line_type) { |note| note.diff_line_type }
+ expose(:path) { |note| note.diff_file_path if note.legacy_diff_note? }
+ expose(:line) { |note| note.diff_new_line if note.legacy_diff_note? }
+ expose(:line_type) { |note| note.diff_line_type if note.legacy_diff_note? }
expose :author, using: Entities::UserBasic
expose :created_at
end
@@ -307,6 +308,10 @@ module API
class Label < Grape::Entity
expose :name, :color, :description
expose :open_issues_count, :closed_issues_count, :open_merge_requests_count
+
+ expose :subscribed do |label, options|
+ label.subscribed?(options[:current_user])
+ end
end
class Compare < Grape::Entity
@@ -357,6 +362,7 @@ module API
expose :restricted_signup_domains
expose :user_oauth_applications
expose :after_sign_out_path
+ expose :container_registry_token_expire_delay
end
class Release < Grape::Entity
@@ -403,6 +409,7 @@ module API
class RunnerDetails < Runner
expose :tag_list
+ expose :run_untagged
expose :version, :revision, :platform, :architecture
expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
@@ -451,5 +458,13 @@ module API
expose(:limitations) { |license| license.meta['limitations'] }
expose :content
end
+
+ class GitignoresList < Grape::Entity
+ expose :name
+ end
+
+ class Gitignore < Grape::Entity
+ expose :name, :content
+ end
end
end
diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb
new file mode 100644
index 00000000000..270c9501dd2
--- /dev/null
+++ b/lib/api/gitignores.rb
@@ -0,0 +1,29 @@
+module API
+ class Gitignores < Grape::API
+
+ # Get the list of the available gitignore templates
+ #
+ # Example Request:
+ # GET /gitignores
+ get 'gitignores' do
+ present Gitlab::Gitignore.all, with: Entities::GitignoresList
+ end
+
+ # Get the text for a specific gitignore
+ #
+ # Parameters:
+ # name (required) - The name of a license
+ #
+ # Example Request:
+ # GET /gitignores/Elixir
+ #
+ get 'gitignores/:name' do
+ required_attributes! [:name]
+
+ gitignore = Gitlab::Gitignore.find(params[:name])
+ not_found!('.gitignore') unless gitignore
+
+ present gitignore, with: Entities::Gitignore
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 91e420832f3..9d8b8d737a9 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -95,8 +95,7 @@ module API
# GET /groups/:id/projects
get ":id/projects" do
group = find_group(params[:id])
- projects = group.projects
- projects = filter_projects(projects)
+ projects = GroupProjectsFinder.new(group).execute(current_user)
projects = paginate projects
present projects, with: Entities::Project
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 40c967453fb..2aaa0557ea3 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -2,7 +2,7 @@ module API
module Helpers
PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
PRIVATE_TOKEN_PARAM = :private_token
- SUDO_HEADER ="HTTP_SUDO"
+ SUDO_HEADER = "HTTP_SUDO"
SUDO_PARAM = :sudo
def parse_boolean(value)
@@ -29,7 +29,7 @@ module API
@current_user
end
- def sudo_identifier()
+ def sudo_identifier
identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers
@@ -95,6 +95,17 @@ module API
end
end
+ def find_project_label(id)
+ label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id)
+ label || not_found!('Label')
+ end
+
+ def find_project_issue(id)
+ issue = user_project.issues.find(id)
+ not_found! unless can?(current_user, :read_issue, issue)
+ issue
+ end
+
def paginate(relation)
relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
add_pagination_headers(data)
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 40928749481..f59a4d6c012 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -103,8 +103,7 @@ module API
# Example Request:
# GET /projects/:id/issues/:issue_id
get ":id/issues/:issue_id" do
- @issue = user_project.issues.find(params[:issue_id])
- not_found! unless can?(current_user, :read_issue, @issue)
+ @issue = find_project_issue(params[:issue_id])
present @issue, with: Entities::Issue, current_user: current_user
end
@@ -234,42 +233,6 @@ module API
authorize!(:destroy_issue, issue)
issue.destroy
end
-
- # Subscribes to a project issue
- #
- # Parameters:
- # id (required) - The ID of a project
- # issue_id (required) - The ID of a project issue
- # Example Request:
- # POST /projects/:id/issues/:issue_id/subscription
- post ':id/issues/:issue_id/subscription' do
- issue = user_project.issues.find(params[:issue_id])
-
- if issue.subscribed?(current_user)
- not_modified!
- else
- issue.toggle_subscription(current_user)
- present issue, with: Entities::Issue, current_user: current_user
- end
- end
-
- # Unsubscribes from a project issue
- #
- # Parameters:
- # id (required) - The ID of a project
- # issue_id (required) - The ID of a project issue
- # Example Request:
- # DELETE /projects/:id/issues/:issue_id/subscription
- delete ':id/issues/:issue_id/subscription' do
- issue = user_project.issues.find(params[:issue_id])
-
- if issue.subscribed?(current_user)
- issue.unsubscribe(current_user)
- present issue, with: Entities::Issue, current_user: current_user
- else
- not_modified!
- end
- end
end
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 4af6bef0fa7..c806829d69e 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -11,7 +11,7 @@ module API
# Example Request:
# GET /projects/:id/labels
get ':id/labels' do
- present user_project.labels, with: Entities::Label
+ present user_project.labels, with: Entities::Label, current_user: current_user
end
# Creates a new label
@@ -36,7 +36,7 @@ module API
label = user_project.labels.create(attrs)
if label.valid?
- present label, with: Entities::Label
+ present label, with: Entities::Label, current_user: current_user
else
render_validation_error!(label)
end
@@ -90,7 +90,7 @@ module API
attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name)
if label.update(attrs)
- present label, with: Entities::Label
+ present label, with: Entities::Label, current_user: current_user
else
render_validation_error!(label)
end
diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb
index 187d2c04703..be0e113fbcb 100644
--- a/lib/api/licenses.rb
+++ b/lib/api/licenses.rb
@@ -2,15 +2,15 @@ module API
# Licenses API
class Licenses < Grape::API
PROJECT_TEMPLATE_REGEX =
- /[\<\{\[]
- (project|description|
- one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]/xi.freeze
+ /[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]/xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
- /[\<\{\[]
- (fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]/xi.freeze
+ /[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]/xi.freeze
# Get the list of the available license templates
#
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 7e78609ecb9..4e7de8867b4 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -327,42 +327,6 @@ module API
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user
end
-
- # Subscribes to a merge request
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of a merge request
- # Example Request:
- # POST /projects/:id/issues/:merge_request_id/subscription
- post "#{path}/subscription" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- if merge_request.subscribed?(current_user)
- not_modified!
- else
- merge_request.toggle_subscription(current_user)
- present merge_request, with: Entities::MergeRequest, current_user: current_user
- end
- end
-
- # Unsubscribes from a merge request
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of a merge request
- # Example Request:
- # DELETE /projects/:id/merge_requests/:merge_request_id/subscription
- delete "#{path}/subscription" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- if merge_request.subscribed?(current_user)
- merge_request.unsubscribe(current_user)
- present merge_request, with: Entities::MergeRequest, current_user: current_user
- else
- not_modified!
- end
- end
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 71a53e6f0d6..d4fcfd3d4d3 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -19,20 +19,24 @@ module API
# GET /projects/:id/issues/:noteable_id/notes
# GET /projects/:id/snippets/:noteable_id/notes
get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
- @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
-
- # We exclude notes that are cross-references and that cannot be viewed
- # by the current user. By doing this exclusion at this level and not
- # at the DB query level (which we cannot in that case), the current
- # page can have less elements than :per_page even if
- # there's more than one page.
- notes =
- # paginate() only works with a relation. This could lead to a
- # mismatch between the pagination headers info and the actual notes
- # array returned, but this is really a edge-case.
- paginate(@noteable.notes).
- reject { |n| n.cross_reference_not_visible_for?(current_user) }
- present notes, with: Entities::Note
+ @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym])
+
+ if can?(current_user, noteable_read_ability_name(@noteable), @noteable)
+ # We exclude notes that are cross-references and that cannot be viewed
+ # by the current user. By doing this exclusion at this level and not
+ # at the DB query level (which we cannot in that case), the current
+ # page can have less elements than :per_page even if
+ # there's more than one page.
+ notes =
+ # paginate() only works with a relation. This could lead to a
+ # mismatch between the pagination headers info and the actual notes
+ # array returned, but this is really a edge-case.
+ paginate(@noteable.notes).
+ reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ present notes, with: Entities::Note
+ else
+ not_found!("Notes")
+ end
end
# Get a single +noteable+ note
@@ -45,13 +49,14 @@ module API
# GET /projects/:id/issues/:noteable_id/notes/:note_id
# GET /projects/:id/snippets/:noteable_id/notes/:note_id
get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
- @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
+ @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym])
@note = @noteable.notes.find(params[:note_id])
+ can_read_note = can?(current_user, noteable_read_ability_name(@noteable), @noteable) && !@note.cross_reference_not_visible_for?(current_user)
- if @note.cross_reference_not_visible_for?(current_user)
- not_found!("Note")
- else
+ if can_read_note
present @note, with: Entities::Note
+ else
+ not_found!("Note")
end
end
@@ -136,5 +141,11 @@ module API
end
end
end
+
+ helpers do
+ def noteable_read_ability_name(noteable)
+ "read_#{noteable.class.to_s.underscore.downcase}".to_sym
+ end
+ end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index cc2c7a0c503..5a22d14988f 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -44,7 +44,7 @@ module API
# Example Request:
# GET /projects/starred
get '/starred' do
- @projects = current_user.starred_projects
+ @projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::Project
@@ -94,6 +94,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# namespace_id (optional) - defaults to user namespace
# public (optional) - if true same as setting visibility_level = 20
@@ -112,6 +113,7 @@ module API
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
+ :container_registry_enabled,
:shared_runners_enabled,
:namespace_id,
:public,
@@ -143,6 +145,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional)
@@ -206,6 +209,7 @@ module API
# builds_enabled (optional)
# wiki_enabled (optional)
# snippets_enabled (optional)
+ # container_registry_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
@@ -222,6 +226,7 @@ module API
:builds_enabled,
:wiki_enabled,
:snippets_enabled,
+ :container_registry_enabled,
:shared_runners_enabled,
:public,
:visibility_level,
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 8ec91485b26..4faba9dc87b 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -49,7 +49,7 @@ module API
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
- attrs = attributes_for_keys [:description, :active, :tag_list]
+ attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged]
if runner.update(attrs)
present runner, with: Entities::RunnerDetails, current_user: current_user
else
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
new file mode 100644
index 00000000000..c49e2a21b82
--- /dev/null
+++ b/lib/api/subscriptions.rb
@@ -0,0 +1,60 @@
+module API
+ class Subscriptions < Grape::API
+ before { authenticate! }
+
+ subscribable_types = {
+ 'merge_request' => proc { |id| user_project.merge_requests.find(id) },
+ 'merge_requests' => proc { |id| user_project.merge_requests.find(id) },
+ 'issues' => proc { |id| find_project_issue(id) },
+ 'labels' => proc { |id| find_project_label(id) },
+ }
+
+ resource :projects do
+ subscribable_types.each do |type, finder|
+ type_singularized = type.singularize
+ type_id_str = :"#{type_singularized}_id"
+ entity_class = Entities.const_get(type_singularized.camelcase)
+
+ # Subscribe to a resource
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # subscribable_id (required) - The ID of a resource
+ # Example Request:
+ # POST /projects/:id/labels/:subscribable_id/subscription
+ # POST /projects/:id/issues/:subscribable_id/subscription
+ # POST /projects/:id/merge_requests/:subscribable_id/subscription
+ post ":id/#{type}/:#{type_id_str}/subscription" do
+ resource = instance_exec(params[type_id_str], &finder)
+
+ if resource.subscribed?(current_user)
+ not_modified!
+ else
+ resource.subscribe(current_user)
+ present resource, with: entity_class, current_user: current_user
+ end
+ end
+
+ # Unsubscribe from a resource
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # subscribable_id (required) - The ID of a resource
+ # Example Request:
+ # DELETE /projects/:id/labels/:subscribable_id/subscription
+ # DELETE /projects/:id/issues/:subscribable_id/subscription
+ # DELETE /projects/:id/merge_requests/:subscribable_id/subscription
+ delete ":id/#{type}/:#{type_id_str}/subscription" do
+ resource = instance_exec(params[type_id_str], &finder)
+
+ if !resource.subscribed?(current_user)
+ not_modified!
+ else
+ resource.unsubscribe(current_user)
+ present resource, with: entity_class, current_user: current_user
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index ea6fa2dc8a8..8a376d3c2a3 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -76,7 +76,7 @@ module API
required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external]
admin = attrs.delete(:admin)
- confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
+ confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i)
user = User.build_user(attrs)
user.admin = admin unless admin.nil?
user.skip_confirmation! unless confirm
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 4962f5e53ce..660ca8c2923 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,5 +1,8 @@
module Backup
class Manager
+ ARCHIVES_TO_BACKUP = %w[uploads builds artifacts lfs registry]
+ FOLDERS_TO_BACKUP = %w[repositories db]
+
def pack
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
@@ -45,7 +48,7 @@ module Backup
end
connection = ::Fog::Storage.new(connection_settings)
- directory = connection.directories.get(remote_directory)
+ directory = connection.directories.create(key: remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
@@ -147,7 +150,7 @@ module Backup
end
def skipped?(item)
- settings[:skipped] && settings[:skipped].include?(item)
+ settings[:skipped] && settings[:skipped].include?(item) || disabled_features.include?(item)
end
private
@@ -157,11 +160,17 @@ module Backup
end
def archives_to_backup
- %w{uploads builds artifacts lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
+ ARCHIVES_TO_BACKUP.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
end
def folders_to_backup
- %w{repositories db}.reject{ |name| skipped?(name) }
+ FOLDERS_TO_BACKUP.reject{ |name| skipped?(name) }
+ end
+
+ def disabled_features
+ features = []
+ features << 'registry' unless Gitlab.config.registry.enabled
+ features
end
def settings
diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb
new file mode 100644
index 00000000000..67fe0231087
--- /dev/null
+++ b/lib/backup/registry.rb
@@ -0,0 +1,13 @@
+require 'backup/files'
+
+module Backup
+ class Registry < Files
+ def initialize
+ super('registry', Settings.registry.path)
+ end
+
+ def create_files_dir
+ Dir.mkdir(app_files_dir, 0700)
+ end
+ end
+end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index b8962379cb5..db95d7c908b 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -18,10 +18,6 @@ module Banzai
@object_sym ||= object_name.to_sym
end
- def self.data_reference
- @data_reference ||= "data-#{object_name.dasherize}"
- end
-
def self.object_class_title
@object_title ||= object_class.name.titleize
end
@@ -45,10 +41,6 @@ module Banzai
end
end
- def self.referenced_by(node)
- { object_sym => LazyReference.new(object_class, node.attr(data_reference)) }
- end
-
def object_class
self.class.object_class
end
@@ -236,7 +228,9 @@ module Banzai
if cache.key?(key)
cache[key]
else
- cache[key] = yield
+ value = yield
+ cache[key] = value if key.present?
+ value
end
end
end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index b469ea0f626..bbb88c979cc 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -4,6 +4,8 @@ module Banzai
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :commit_range
+
def self.object_class
CommitRange
end
@@ -14,34 +16,18 @@ module Banzai
end
end
- def self.referenced_by(node)
- project = Project.find(node.attr("data-project")) rescue nil
- return unless project
-
- id = node.attr("data-commit-range")
- range = find_object(project, id)
-
- return unless range
-
- { commit_range: range }
- end
-
def initialize(*args)
super
@commit_map = {}
end
- def self.find_object(project, id)
+ def find_object(project, id)
range = CommitRange.new(id, project)
range.valid_commits? ? range : nil
end
- def find_object(*args)
- self.class.find_object(*args)
- end
-
def url_for_object(range, project)
h = Gitlab::Routing.url_helpers
h.namespace_project_compare_url(project.namespace, project,
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index bd88207326c..2ce1816672b 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -4,6 +4,8 @@ module Banzai
#
# This filter supports cross-project references.
class CommitReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :commit
+
def self.object_class
Commit
end
@@ -14,28 +16,12 @@ module Banzai
end
end
- def self.referenced_by(node)
- project = Project.find(node.attr("data-project")) rescue nil
- return unless project
-
- id = node.attr("data-commit")
- commit = find_object(project, id)
-
- return unless commit
-
- { commit: commit }
- end
-
- def self.find_object(project, id)
+ def find_object(project, id)
if project && project.valid_repo?
project.commit(id)
end
end
- def find_object(*args)
- self.class.find_object(*args)
- end
-
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index 37344b90576..eaa702952cc 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -4,6 +4,8 @@ module Banzai
# References are ignored if the project doesn't use an external issue
# tracker.
class ExternalIssueReferenceFilter < ReferenceFilter
+ self.reference_type = :external_issue
+
# Public: Find `JIRA-123` issue references in text
#
# ExternalIssueReferenceFilter.references_in(text) do |match, issue|
@@ -21,18 +23,6 @@ module Banzai
end
end
- def self.referenced_by(node)
- project = Project.find(node.attr("data-project")) rescue nil
- return unless project
-
- id = node.attr("data-external-issue")
- external_issue = ExternalIssue.new(id, project)
-
- return unless external_issue
-
- { external_issue: external_issue }
- end
-
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || default_issues_tracker?
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
new file mode 100644
index 00000000000..beb21b19ab3
--- /dev/null
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -0,0 +1,26 @@
+module Banzai
+ module Filter
+ class InlineDiffFilter < HTML::Pipeline::Filter
+ IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
+
+ def call
+ search_text_nodes(doc).each do |node|
+ next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
+
+ content = node.to_html
+ html_content = inline_diff_filter(content)
+
+ next if content == html_content
+
+ node.replace(html_content)
+ end
+ doc
+ end
+
+ def inline_diff_filter(text)
+ html_content = text.gsub(/(?:\[\-(.*?)\-\]|\{\-(.*?)\-\})/, '<span class="idiff left right deletion">\1\2</span>')
+ html_content.gsub(/(?:\[\+(.*?)\+\]|\{\+(.*?)\+\})/, '<span class="idiff left right addition">\1\2</span>')
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 2732e0b5145..2496e704002 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -5,15 +5,12 @@ module Banzai
#
# This filter supports cross-project references.
class IssueReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :issue
+
def self.object_class
Issue
end
- def self.user_can_see_reference?(user, node, context)
- issue = Issue.find(node.attr('data-issue')) rescue nil
- Ability.abilities.allowed?(user, :read_issue, issue)
- end
-
def find_object(project, id)
project.get_issue(id)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 8488a493b55..e4d3f87d0aa 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -2,6 +2,8 @@ module Banzai
module Filter
# HTML filter that replaces label references with links.
class LabelReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :label
+
def self.object_class
Label
end
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index cad38a51851..ac5216d9cfb 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -5,6 +5,8 @@ module Banzai
#
# This filter supports cross-project references.
class MergeRequestReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :merge_request
+
def self.object_class
MergeRequest
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 4cb82178024..ca686c87d97 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -2,6 +2,8 @@ module Banzai
module Filter
# HTML filter that replaces milestone references with links.
class MilestoneReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :milestone
+
def self.object_class
Milestone
end
@@ -10,11 +12,53 @@ module Banzai
project.milestones.find_by(iid: id)
end
- def url_for_object(issue, project)
+ def references_in(text, pattern = Milestone.reference_pattern)
+ # We'll handle here the references that follow the `reference_pattern`.
+ # Other patterns (for example, the link pattern) are handled by the
+ # default implementation.
+ return super(text, pattern) if pattern != Milestone.reference_pattern
+
+ text.gsub(pattern) do |match|
+ milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name])
+
+ if milestone
+ yield match, milestone.iid, $~[:project], $~
+ else
+ match
+ end
+ end
+ end
+
+ def find_milestone(project_ref, milestone_id, milestone_name)
+ project = project_from_ref(project_ref)
+ return unless project
+
+ milestone_params = milestone_params(milestone_id, milestone_name)
+ project.milestones.find_by(milestone_params)
+ end
+
+ def milestone_params(iid, name)
+ if name
+ { name: name.tr('"', '') }
+ else
+ { iid: iid.to_i }
+ end
+ end
+
+ def url_for_object(milestone, project)
h = Gitlab::Routing.url_helpers
h.namespace_project_milestone_url(project.namespace, project, milestone,
only_path: context[:only_path])
end
+
+ def object_link_text(object, matches)
+ if context[:project] == object.project
+ super
+ else
+ "#{escape_once(super)} <i>in #{escape_once(object.project.path_with_namespace)}</i>".
+ html_safe
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index e589b5df6ec..c753a84a20d 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,8 +7,11 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- Querying.css(doc, 'a.gfm').each do |node|
- unless user_can_see_reference?(node)
+ nodes = Querying.css(doc, 'a.gfm[data-reference-type]')
+ visible = nodes_visible_to_user(nodes)
+
+ nodes.each do |node|
+ unless visible.include?(node)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
text = node.attr('data-original') || node.text
@@ -21,20 +24,30 @@ module Banzai
private
- def user_can_see_reference?(node)
- if node.has_attribute?('data-reference-filter')
- reference_type = node.attr('data-reference-filter')
- reference_filter = Banzai::Filter.const_get(reference_type)
+ def nodes_visible_to_user(nodes)
+ per_type = Hash.new { |h, k| h[k] = [] }
+ visible = Set.new
+
+ nodes.each do |node|
+ per_type[node.attr('data-reference-type')] << node
+ end
+
+ per_type.each do |type, nodes|
+ parser = Banzai::ReferenceParser[type].new(project, current_user)
- reference_filter.user_can_see_reference?(current_user, node, context)
- else
- true
+ visible.merge(parser.nodes_visible_to_user(current_user, nodes))
end
+
+ visible
end
def current_user
context[:current_user]
end
+
+ def project
+ context[:project]
+ end
end
end
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 31386cf851c..41ae0e1f9cc 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -8,24 +8,8 @@ module Banzai
# :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links.
class ReferenceFilter < HTML::Pipeline::Filter
- def self.user_can_see_reference?(user, node, context)
- if node.has_attribute?('data-project')
- project_id = node.attr('data-project').to_i
- return true if project_id == context[:project].try(:id)
-
- project = Project.find(project_id) rescue nil
- Ability.abilities.allowed?(user, :read_project, project)
- else
- true
- end
- end
-
- def self.user_can_reference?(user, node, context)
- true
- end
-
- def self.referenced_by(node)
- raise NotImplementedError, "#{self} does not implement #{__method__}"
+ class << self
+ attr_accessor :reference_type
end
# Returns a data attribute String to attach to a reference link
@@ -43,7 +27,9 @@ module Banzai
#
# Returns a String
def data_attribute(attributes = {})
- attributes[:reference_filter] = self.class.name.demodulize
+ attributes = attributes.reject { |_, v| v.nil? }
+
+ attributes[:reference_type] = self.class.reference_type
attributes.delete(:original) if context[:no_original_data]
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
end
diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb
deleted file mode 100644
index 96fdb06304e..00000000000
--- a/lib/banzai/filter/reference_gatherer_filter.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module Banzai
- module Filter
- # HTML filter that gathers all referenced records that the current user has
- # permission to view.
- #
- # Expected to be run in its own post-processing pipeline.
- #
- class ReferenceGathererFilter < HTML::Pipeline::Filter
- def initialize(*)
- super
-
- result[:references] ||= Hash.new { |hash, type| hash[type] = [] }
- end
-
- def call
- Querying.css(doc, 'a.gfm').each do |node|
- gather_references(node)
- end
-
- load_lazy_references unless ReferenceExtractor.lazy?
-
- doc
- end
-
- private
-
- def gather_references(node)
- return unless node.has_attribute?('data-reference-filter')
-
- reference_type = node.attr('data-reference-filter')
- reference_filter = Banzai::Filter.const_get(reference_type)
-
- return if context[:reference_filter] && reference_filter != context[:reference_filter]
-
- return if author && !reference_filter.user_can_reference?(author, node, context)
-
- return unless reference_filter.user_can_see_reference?(current_user, node, context)
-
- references = reference_filter.referenced_by(node)
- return unless references
-
- references.each do |type, values|
- Array.wrap(values).each do |value|
- result[:references][type] << value
- end
- end
- end
-
- def load_lazy_references
- refs = result[:references]
- refs.each do |type, values|
- refs[type] = ReferenceExtractor.lazily(values)
- end
- end
-
- def current_user
- context[:current_user]
- end
-
- def author
- context[:author]
- end
- end
- end
-end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 42dbab9d27e..ca80aac5a08 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -63,7 +63,7 @@ module Banzai
begin
uri = Addressable::URI.parse(node['href'])
- uri.scheme.strip! if uri.scheme
+ uri.scheme = uri.scheme.strip.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index d507eb5ebe1..212a0bbf2a0 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -5,6 +5,8 @@ module Banzai
#
# This filter supports cross-project references.
class SnippetReferenceFilter < AbstractReferenceFilter
+ self.reference_type = :snippet
+
def self.object_class
Snippet
end
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
index 7edfe5ade2d..c0f503c9af3 100644
--- a/lib/banzai/filter/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -8,6 +8,8 @@ module Banzai
#
class UploadLinkFilter < HTML::Pipeline::Filter
def call
+ return doc unless project
+
doc.search('a').each do |el|
process_link_attr el.attribute('href')
end
@@ -31,7 +33,11 @@ module Banzai
end
def build_url(uri)
- File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri)
+ File.join(Gitlab.config.gitlab.url, project.path_with_namespace, uri)
+ end
+
+ def project
+ context[:project]
end
# Ensure that a :project key exists in context
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index eea3af842b6..331d8007257 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -4,6 +4,8 @@ module Banzai
#
# A special `@all` reference is also supported.
class UserReferenceFilter < ReferenceFilter
+ self.reference_type = :user
+
# Public: Find `@user` user references in text
#
# UserReferenceFilter.references_in(text) do |match, username|
@@ -21,43 +23,6 @@ module Banzai
end
end
- def self.referenced_by(node)
- if node.has_attribute?('data-group')
- group = Group.find(node.attr('data-group')) rescue nil
- return unless group
-
- { user: group.users }
- elsif node.has_attribute?('data-user')
- { user: LazyReference.new(User, node.attr('data-user')) }
- elsif node.has_attribute?('data-project')
- project = Project.find(node.attr('data-project')) rescue nil
- return unless project
-
- { user: project.team.members.flatten }
- end
- end
-
- def self.user_can_see_reference?(user, node, context)
- if node.has_attribute?('data-group')
- group = Group.find(node.attr('data-group')) rescue nil
- Ability.abilities.allowed?(user, :read_group, group)
- else
- super
- end
- end
-
- def self.user_can_reference?(user, node, context)
- # Only team members can reference `@all`
- if node.has_attribute?('data-project')
- project = Project.find(node.attr('data-project')) rescue nil
- return false unless project
-
- user && project.team.member?(user)
- else
- super
- end
- end
-
def call
return doc if project.nil?
@@ -114,9 +79,12 @@ module Banzai
def link_to_all(link_text: nil)
project = context[:project]
+ author = context[:author]
+
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
- data = data_attribute(project: project.id)
+
+ data = data_attribute(project: project.id, author: author.try(:id))
text = link_text || User.reference_prefix + 'all'
link_tag(url, data, text)
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 06d10c98501..7dc771afd71 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -25,7 +25,7 @@ module Banzai
end
def process_link_attr(html_attr)
- return if html_attr.blank? || file_reference?(html_attr)
+ return if html_attr.blank? || file_reference?(html_attr) || hierarchical_link?(html_attr)
uri = URI(html_attr.value)
if uri.relative? && uri.path.present?
@@ -40,12 +40,17 @@ module Banzai
uri
end
+ def project_wiki
+ context[:project_wiki]
+ end
+
def file_reference?(html_attr)
!File.extname(html_attr.value).blank?
end
- def project_wiki
- context[:project_wiki]
+ # Of the form `./link`, `../link`, or similar
+ def hierarchical_link?(html_attr)
+ html_attr.value[0] == '.'
end
def project_wiki_base_path
diff --git a/lib/banzai/lazy_reference.rb b/lib/banzai/lazy_reference.rb
deleted file mode 100644
index 1095b4debc7..00000000000
--- a/lib/banzai/lazy_reference.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Banzai
- class LazyReference
- def self.load(refs)
- lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
-
- lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
- ids = refs.flat_map(&:ids)
- klass.where(id: ids)
- end
-
- values + lazy_values
- end
-
- attr_reader :klass, :ids
-
- def initialize(klass, ids)
- @klass = klass
- @ids = Array.wrap(ids).map(&:to_i)
- end
-
- def load
- self.klass.where(id: self.ids)
- end
- end
-end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index ed3cfd6b023..b27ecf3c923 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -23,7 +23,8 @@ module Banzai
Filter::LabelReferenceFilter,
Filter::MilestoneReferenceFilter,
- Filter::TaskListFilter
+ Filter::TaskListFilter,
+ Filter::InlineDiffFilter
]
end
diff --git a/lib/banzai/pipeline/reference_extraction_pipeline.rb b/lib/banzai/pipeline/reference_extraction_pipeline.rb
deleted file mode 100644
index 919998380e4..00000000000
--- a/lib/banzai/pipeline/reference_extraction_pipeline.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Banzai
- module Pipeline
- class ReferenceExtractionPipeline < BasePipeline
- def self.filters
- FilterArray[
- Filter::ReferenceGathererFilter
- ]
- end
- end
- end
-end
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index f4079538ec5..bf366962aef 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -1,28 +1,6 @@
module Banzai
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- class << self
- LAZY_KEY = :banzai_reference_extractor_lazy
-
- def lazy?
- Thread.current[LAZY_KEY]
- end
-
- def lazily(values = nil, &block)
- return (values || block.call).uniq if lazy?
-
- begin
- Thread.current[LAZY_KEY] = true
-
- values ||= block.call
-
- Banzai::LazyReference.load(values.uniq).uniq
- ensure
- Thread.current[LAZY_KEY] = false
- end
- end
- end
-
def initialize
@texts = []
end
@@ -31,23 +9,21 @@ module Banzai
@texts << Renderer.render(text, context)
end
- def references(type, context = {})
- filter = Banzai::Filter["#{type}_reference"]
+ def references(type, project, current_user = nil)
+ processor = Banzai::ReferenceParser[type].
+ new(project, current_user)
+
+ processor.process(html_documents)
+ end
- context.merge!(
- pipeline: :reference_extraction,
+ private
- # ReferenceGathererFilter
- reference_filter: filter
- )
+ def html_documents
+ # This ensures that we don't memoize anything until we have a number of
+ # text blobs to parse.
+ return [] if @texts.empty?
- self.class.lazily do
- @texts.flat_map do |html|
- text_context = context.dup
- result = Renderer.render_result(html, text_context)
- result[:references][type]
- end.uniq
- end
+ @html_documents ||= @texts.map { |html| Nokogiri::HTML.fragment(html) }
end
end
end
diff --git a/lib/banzai/reference_parser.rb b/lib/banzai/reference_parser.rb
new file mode 100644
index 00000000000..557bec4316e
--- /dev/null
+++ b/lib/banzai/reference_parser.rb
@@ -0,0 +1,14 @@
+module Banzai
+ module ReferenceParser
+ # Returns the reference parser class for the given type
+ #
+ # Example:
+ #
+ # Banzai::ReferenceParser['issue']
+ #
+ # This would return the `Banzai::ReferenceParser::IssueParser` class.
+ def self.[](name)
+ const_get("#{name.to_s.camelize}Parser")
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
new file mode 100644
index 00000000000..3d7b9c4a024
--- /dev/null
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -0,0 +1,204 @@
+module Banzai
+ module ReferenceParser
+ # Base class for reference parsing classes.
+ #
+ # Each parser should also specify its reference type by calling
+ # `self.reference_type = ...` in the body of the class. The value of this
+ # method should be a symbol such as `:issue` or `:merge_request`. For
+ # example:
+ #
+ # class IssueParser < BaseParser
+ # self.reference_type = :issue
+ # end
+ #
+ # The reference type is used to determine what nodes to pass to the
+ # `referenced_by` method.
+ #
+ # Parser classes should either implement the instance method
+ # `references_relation` or overwrite `referenced_by`. The
+ # `references_relation` method is supposed to return an
+ # ActiveRecord::Relation used as a base relation for retrieving the objects
+ # referenced in a set of HTML nodes.
+ #
+ # Each class can implement two additional methods:
+ #
+ # * `nodes_user_can_reference`: returns an Array of nodes the given user can
+ # refer to.
+ # * `nodes_visible_to_user`: returns an Array of nodes that are visible to
+ # the given user.
+ #
+ # You only need to overwrite these methods if you want to tweak who can see
+ # which references. For example, the IssueParser class defines its own
+ # `nodes_visible_to_user` method so it can ensure users can only see issues
+ # they have access to.
+ class BaseParser
+ class << self
+ attr_accessor :reference_type
+ end
+
+ # Returns the attribute name containing the value for every object to be
+ # parsed by the current parser.
+ #
+ # For example, for a parser class that returns "Animal" objects this
+ # attribute would be "data-animal".
+ def self.data_attribute
+ @data_attribute ||= "data-#{reference_type.to_s.dasherize}"
+ end
+
+ def initialize(project = nil, current_user = nil)
+ @project = project
+ @current_user = current_user
+ end
+
+ # Returns all the nodes containing references that the user can refer to.
+ def nodes_user_can_reference(user, nodes)
+ nodes
+ end
+
+ # Returns all the nodes that are visible to the given user.
+ def nodes_visible_to_user(user, nodes)
+ projects = lazy { projects_for_nodes(nodes) }
+ project_attr = 'data-project'
+
+ nodes.select do |node|
+ if node.has_attribute?(project_attr)
+ node_id = node.attr(project_attr).to_i
+
+ if project && project.id == node_id
+ true
+ else
+ can?(user, :read_project, projects[node_id])
+ end
+ else
+ true
+ end
+ end
+ end
+
+ # Returns an Array of objects referenced by any of the given HTML nodes.
+ def referenced_by(nodes)
+ ids = unique_attribute_values(nodes, self.class.data_attribute)
+
+ references_relation.where(id: ids)
+ end
+
+ # Returns the ActiveRecord::Relation to use for querying references in the
+ # DB.
+ def references_relation
+ raise NotImplementedError,
+ "#{self.class} does not implement #{__method__}"
+ end
+
+ # Returns a Hash containing attribute values per project ID.
+ #
+ # The returned Hash uses the following format:
+ #
+ # { project id => [value1, value2, ...] }
+ #
+ # nodes - An Array of HTML nodes to process.
+ # attribute - The name of the attribute (as a String) for which to gather
+ # values.
+ #
+ # Returns a Hash.
+ def gather_attributes_per_project(nodes, attribute)
+ per_project = Hash.new { |hash, key| hash[key] = Set.new }
+
+ nodes.each do |node|
+ project_id = node.attr('data-project').to_i
+ id = node.attr(attribute)
+
+ per_project[project_id] << id if id
+ end
+
+ per_project
+ end
+
+ # Returns a Hash containing objects for an attribute grouped per their
+ # IDs.
+ #
+ # The returned Hash uses the following format:
+ #
+ # { id value => row }
+ #
+ # nodes - An Array of HTML nodes to process.
+ #
+ # collection - The model or ActiveRecord relation to use for retrieving
+ # rows from the database.
+ #
+ # attribute - The name of the attribute containing the primary key values
+ # for every row.
+ #
+ # Returns a Hash.
+ def grouped_objects_for_nodes(nodes, collection, attribute)
+ return {} if nodes.empty?
+
+ ids = unique_attribute_values(nodes, attribute)
+
+ collection.where(id: ids).each_with_object({}) do |row, hash|
+ hash[row.id] = row
+ end
+ end
+
+ # Returns an Array containing all unique values of an attribute of the
+ # given nodes.
+ def unique_attribute_values(nodes, attribute)
+ values = Set.new
+
+ nodes.each do |node|
+ if node.has_attribute?(attribute)
+ values << node.attr(attribute)
+ end
+ end
+
+ values.to_a
+ end
+
+ # Processes the list of HTML documents and returns an Array containing all
+ # the references.
+ def process(documents)
+ type = self.class.reference_type
+
+ nodes = documents.flat_map do |document|
+ Querying.css(document, "a[data-reference-type='#{type}'].gfm").to_a
+ end
+
+ gather_references(nodes)
+ end
+
+ # Gathers the references for the given HTML nodes.
+ def gather_references(nodes)
+ nodes = nodes_user_can_reference(current_user, nodes)
+ nodes = nodes_visible_to_user(current_user, nodes)
+
+ referenced_by(nodes)
+ end
+
+ # Returns a Hash containing the projects for a given list of HTML nodes.
+ #
+ # The returned Hash uses the following format:
+ #
+ # { project ID => project }
+ #
+ def projects_for_nodes(nodes)
+ @projects_for_nodes ||=
+ grouped_objects_for_nodes(nodes, Project, 'data-project')
+ end
+
+ def can?(user, permission, subject)
+ Ability.abilities.allowed?(user, permission, subject)
+ end
+
+ def find_projects_for_hash_keys(hash)
+ Project.where(id: hash.keys)
+ end
+
+ private
+
+ attr_reader :current_user, :project
+
+ def lazy(&block)
+ Gitlab::Lazy.new(&block)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/commit_parser.rb b/lib/banzai/reference_parser/commit_parser.rb
new file mode 100644
index 00000000000..0fee9d267de
--- /dev/null
+++ b/lib/banzai/reference_parser/commit_parser.rb
@@ -0,0 +1,34 @@
+module Banzai
+ module ReferenceParser
+ class CommitParser < BaseParser
+ self.reference_type = :commit
+
+ def referenced_by(nodes)
+ commit_ids = commit_ids_per_project(nodes)
+ projects = find_projects_for_hash_keys(commit_ids)
+
+ projects.flat_map do |project|
+ find_commits(project, commit_ids[project.id])
+ end
+ end
+
+ def commit_ids_per_project(nodes)
+ gather_attributes_per_project(nodes, self.class.data_attribute)
+ end
+
+ def find_commits(project, ids)
+ commits = []
+
+ return commits unless project.valid_repo?
+
+ ids.each do |id|
+ commit = project.commit(id)
+
+ commits << commit if commit
+ end
+
+ commits
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/commit_range_parser.rb b/lib/banzai/reference_parser/commit_range_parser.rb
new file mode 100644
index 00000000000..69d01f8db15
--- /dev/null
+++ b/lib/banzai/reference_parser/commit_range_parser.rb
@@ -0,0 +1,38 @@
+module Banzai
+ module ReferenceParser
+ class CommitRangeParser < BaseParser
+ self.reference_type = :commit_range
+
+ def referenced_by(nodes)
+ range_ids = commit_range_ids_per_project(nodes)
+ projects = find_projects_for_hash_keys(range_ids)
+
+ projects.flat_map do |project|
+ find_ranges(project, range_ids[project.id])
+ end
+ end
+
+ def commit_range_ids_per_project(nodes)
+ gather_attributes_per_project(nodes, self.class.data_attribute)
+ end
+
+ def find_ranges(project, range_ids)
+ ranges = []
+
+ range_ids.each do |id|
+ range = find_object(project, id)
+
+ ranges << range if range
+ end
+
+ ranges
+ end
+
+ def find_object(project, id)
+ range = CommitRange.new(id, project)
+
+ range.valid_commits? ? range : nil
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/external_issue_parser.rb b/lib/banzai/reference_parser/external_issue_parser.rb
new file mode 100644
index 00000000000..a1264db2111
--- /dev/null
+++ b/lib/banzai/reference_parser/external_issue_parser.rb
@@ -0,0 +1,25 @@
+module Banzai
+ module ReferenceParser
+ class ExternalIssueParser < BaseParser
+ self.reference_type = :external_issue
+
+ def referenced_by(nodes)
+ issue_ids = issue_ids_per_project(nodes)
+ projects = find_projects_for_hash_keys(issue_ids)
+ issues = []
+
+ projects.each do |project|
+ issue_ids[project.id].each do |id|
+ issues << ExternalIssue.new(id, project)
+ end
+ end
+
+ issues
+ end
+
+ def issue_ids_per_project(nodes)
+ gather_attributes_per_project(nodes, self.class.data_attribute)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
new file mode 100644
index 00000000000..24076e3d9ec
--- /dev/null
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -0,0 +1,40 @@
+module Banzai
+ module ReferenceParser
+ class IssueParser < BaseParser
+ self.reference_type = :issue
+
+ def nodes_visible_to_user(user, nodes)
+ # It is not possible to check access rights for external issue trackers
+ return nodes if project && project.external_issue_tracker
+
+ issues = issues_for_nodes(nodes)
+
+ nodes.select do |node|
+ issue = issue_for_node(issues, node)
+
+ issue ? can?(user, :read_issue, issue) : false
+ end
+ end
+
+ def referenced_by(nodes)
+ issues = issues_for_nodes(nodes)
+
+ nodes.map { |node| issue_for_node(issues, node) }.uniq
+ end
+
+ def issues_for_nodes(nodes)
+ @issues_for_nodes ||= grouped_objects_for_nodes(
+ nodes,
+ Issue.all.includes(:author, :assignee, :project),
+ self.class.data_attribute
+ )
+ end
+
+ private
+
+ def issue_for_node(issues, node)
+ issues[node.attr(self.class.data_attribute).to_i]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/label_parser.rb b/lib/banzai/reference_parser/label_parser.rb
new file mode 100644
index 00000000000..e5d1eb11d7f
--- /dev/null
+++ b/lib/banzai/reference_parser/label_parser.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module ReferenceParser
+ class LabelParser < BaseParser
+ self.reference_type = :label
+
+ def references_relation
+ Label
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
new file mode 100644
index 00000000000..c9a9ca79c09
--- /dev/null
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module ReferenceParser
+ class MergeRequestParser < BaseParser
+ self.reference_type = :merge_request
+
+ def references_relation
+ MergeRequest.includes(:author, :assignee, :target_project)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/milestone_parser.rb b/lib/banzai/reference_parser/milestone_parser.rb
new file mode 100644
index 00000000000..a000ac61e5c
--- /dev/null
+++ b/lib/banzai/reference_parser/milestone_parser.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module ReferenceParser
+ class MilestoneParser < BaseParser
+ self.reference_type = :milestone
+
+ def references_relation
+ Milestone
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/snippet_parser.rb b/lib/banzai/reference_parser/snippet_parser.rb
new file mode 100644
index 00000000000..fa71b3c952a
--- /dev/null
+++ b/lib/banzai/reference_parser/snippet_parser.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module ReferenceParser
+ class SnippetParser < BaseParser
+ self.reference_type = :snippet
+
+ def references_relation
+ Snippet
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
new file mode 100644
index 00000000000..a12b0d19560
--- /dev/null
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -0,0 +1,92 @@
+module Banzai
+ module ReferenceParser
+ class UserParser < BaseParser
+ self.reference_type = :user
+
+ def referenced_by(nodes)
+ group_ids = []
+ user_ids = []
+ project_ids = []
+
+ nodes.each do |node|
+ if node.has_attribute?('data-group')
+ group_ids << node.attr('data-group').to_i
+ elsif node.has_attribute?(self.class.data_attribute)
+ user_ids << node.attr(self.class.data_attribute).to_i
+ elsif node.has_attribute?('data-project')
+ project_ids << node.attr('data-project').to_i
+ end
+ end
+
+ find_users_for_groups(group_ids) | find_users(user_ids) |
+ find_users_for_projects(project_ids)
+ end
+
+ def nodes_visible_to_user(user, nodes)
+ group_attr = 'data-group'
+ groups = lazy { grouped_objects_for_nodes(nodes, Group, group_attr) }
+ visible = []
+ remaining = []
+
+ nodes.each do |node|
+ if node.has_attribute?(group_attr)
+ node_group = groups[node.attr(group_attr).to_i]
+
+ if node_group &&
+ can?(user, :read_group, node_group)
+ visible << node
+ end
+ # Remaining nodes will be processed by the parent class'
+ # implementation of this method.
+ else
+ remaining << node
+ end
+ end
+
+ visible + super(current_user, remaining)
+ end
+
+ def nodes_user_can_reference(current_user, nodes)
+ project_attr = 'data-project'
+ author_attr = 'data-author'
+
+ projects = lazy { projects_for_nodes(nodes) }
+ users = lazy { grouped_objects_for_nodes(nodes, User, author_attr) }
+
+ nodes.select do |node|
+ project_id = node.attr(project_attr)
+ user_id = node.attr(author_attr)
+
+ if project && project_id && project.id == project_id.to_i
+ true
+ elsif project_id && user_id
+ project = projects[project_id.to_i]
+ user = users[user_id.to_i]
+
+ project && user ? project.team.member?(user) : false
+ else
+ true
+ end
+ end
+ end
+
+ def find_users(ids)
+ return [] if ids.empty?
+
+ User.where(id: ids).to_a
+ end
+
+ def find_users_for_groups(ids)
+ return [] if ids.empty?
+
+ User.joins(:group_members).where(members: { source_id: ids }).to_a
+ end
+
+ def find_users_for_projects(ids)
+ return [] if ids.empty?
+
+ Project.where(id: ids).flat_map { |p| p.team.members.to_a }
+ end
+ end
+ end
+end
diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb
index ac6d667cf8d..229050151d3 100644
--- a/lib/ci/ansi2html.rb
+++ b/lib/ci/ansi2html.rb
@@ -23,8 +23,8 @@ module Ci
cross: 0x10,
}
- def self.convert(ansi)
- Converter.new().convert(ansi)
+ def self.convert(ansi, state = nil)
+ Converter.new.convert(ansi, state)
end
class Converter
@@ -84,22 +84,38 @@ module Ci
def on_107(s) set_bg_color(7, 'l') end
def on_109(s) set_bg_color(9, 'l') end
- def convert(ansi)
- @out = ""
- @n_open_tags = 0
- reset()
+ attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
+
+ STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask]
+
+ def convert(raw, new_state)
+ reset_state
+ restore_state(raw, new_state) if new_state.present?
+
+ start = @offset
+ ansi = raw[@offset..-1]
+
+ open_new_tag
- s = StringScanner.new(ansi.gsub("<", "&lt;"))
- while(!s.eos?)
+ s = StringScanner.new(ansi)
+ until s.eos?
if s.scan(/\e([@-_])(.*?)([@-~])/)
handle_sequence(s)
+ elsif s.scan(/\e(([@-_])(.*?)?)?$/)
+ break
+ elsif s.scan(/</)
+ @out << '&lt;'
+ elsif s.scan(/\n/)
+ @out << '<br>'
else
@out << s.scan(/./m)
end
+ @offset += s.matched_size
end
close_open_tags()
- @out
+
+ { state: state, html: @out, text: ansi[0, @offset - start], append: start > 0 }
end
def handle_sequence(s)
@@ -121,6 +137,20 @@ module Ci
evaluate_command_stack(commands)
+ open_new_tag
+ end
+
+ def evaluate_command_stack(stack)
+ return unless command = stack.shift()
+
+ if self.respond_to?("on_#{command}", true)
+ self.send("on_#{command}", stack)
+ end
+
+ evaluate_command_stack(stack)
+ end
+
+ def open_new_tag
css_classes = []
unless @fg_color.nil?
@@ -138,20 +168,8 @@ module Ci
css_classes << "term-#{css_class}" if @style_mask & flag != 0
end
- open_new_tag(css_classes) if css_classes.length > 0
- end
+ return if css_classes.empty?
- def evaluate_command_stack(stack)
- return unless command = stack.shift()
-
- if self.respond_to?("on_#{command}", true)
- self.send("on_#{command}", stack)
- end
-
- evaluate_command_stack(stack)
- end
-
- def open_new_tag(css_classes)
@out << %{<span class="#{css_classes.join(' ')}">}
@n_open_tags += 1
end
@@ -163,6 +181,31 @@ module Ci
end
end
+ def reset_state
+ @offset = 0
+ @n_open_tags = 0
+ @out = ''
+ reset
+ end
+
+ def state
+ state = STATE_PARAMS.inject({}) do |h, param|
+ h[param] = send(param)
+ h
+ end
+ Base64.urlsafe_encode64(state.to_json)
+ end
+
+ def restore_state(raw, new_state)
+ state = Base64.urlsafe_decode64(new_state)
+ state = JSON.parse(state, symbolize_names: true)
+ return if state[:offset].to_i > raw.length
+
+ STATE_PARAMS.each do |param|
+ send("#{param}=".to_sym, state[param])
+ end
+ end
+
def reset
@fg_color = nil
@bg_color = nil
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 353c4ddebf8..17bb99a2ae5 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -1,9 +1,7 @@
-Dir["#{Rails.root}/lib/ci/api/*.rb"].each {|file| require file}
-
module Ci
module API
class API < Grape::API
- include APIGuard
+ include ::API::APIGuard
version 'v1', using: :path
rescue_from ActiveRecord::RecordNotFound do
@@ -31,9 +29,9 @@ module Ci
helpers ::API::Helpers
helpers Gitlab::CurrentSettings
- mount Builds
- mount Runners
- mount Triggers
+ mount ::Ci::API::Builds
+ mount ::Ci::API::Runners
+ mount ::Ci::API::Triggers
end
end
end
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index 192b1d18a51..0c41f22c7c5 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -28,20 +28,20 @@ module Ci
post "register" do
required_attributes! [:token]
+ attributes = { description: params[:description],
+ tag_list: params[:tag_list] }
+
+ unless params[:run_untagged].nil?
+ attributes[:run_untagged] = params[:run_untagged]
+ end
+
runner =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
- Ci::Runner.create(
- description: params[:description],
- tag_list: params[:tag_list],
- is_shared: true
- )
+ Ci::Runner.create(attributes.merge(is_shared: true))
elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for project.
- project.runners.create(
- description: params[:description],
- tag_list: params[:tag_list]
- )
+ project.runners.create(attributes)
end
return forbidden! unless runner
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index d53bdcbd0f2..e1636636934 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -64,7 +64,8 @@ module Ci
commits.each do |commit|
@labels << commit.short_sha
- @build_times << (commit.duration / 60)
+ duration = commit.duration || 0
+ @build_times << (duration / 60)
end
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 504d3df9d34..026a5ac97ca 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -1,6 +1,6 @@
module Ci
class GitlabCiYamlProcessor
- class ValidationError < StandardError;end
+ class ValidationError < StandardError; end
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
@@ -265,7 +265,7 @@ module Ci
end
def validate_job_dependencies!(name, job)
- if !validate_array_of_strings(job[:dependencies])
+ unless validate_array_of_strings(job[:dependencies])
raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
end
diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb
new file mode 100644
index 00000000000..4e20dc4f875
--- /dev/null
+++ b/lib/container_registry/blob.rb
@@ -0,0 +1,48 @@
+module ContainerRegistry
+ class Blob
+ attr_reader :repository, :config
+
+ delegate :registry, :client, to: :repository
+
+ def initialize(repository, config)
+ @repository = repository
+ @config = config || {}
+ end
+
+ def valid?
+ digest.present?
+ end
+
+ def path
+ "#{repository.path}@#{digest}"
+ end
+
+ def digest
+ config['digest']
+ end
+
+ def type
+ config['mediaType']
+ end
+
+ def size
+ config['size']
+ end
+
+ def revision
+ digest.split(':')[1]
+ end
+
+ def short_revision
+ revision[0..8]
+ end
+
+ def delete
+ client.delete_blob(repository.name, digest)
+ end
+
+ def data
+ @data ||= client.blob(repository.name, digest, type)
+ end
+ end
+end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
new file mode 100644
index 00000000000..4d726692f45
--- /dev/null
+++ b/lib/container_registry/client.rb
@@ -0,0 +1,61 @@
+require 'faraday'
+require 'faraday_middleware'
+
+module ContainerRegistry
+ class Client
+ attr_accessor :uri
+
+ MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'
+
+ def initialize(base_uri, options = {})
+ @base_uri = base_uri
+ @faraday = Faraday.new(@base_uri) do |conn|
+ initialize_connection(conn, options)
+ end
+ end
+
+ def repository_tags(name)
+ @faraday.get("/v2/#{name}/tags/list").body
+ end
+
+ def repository_manifest(name, reference)
+ @faraday.get("/v2/#{name}/manifests/#{reference}").body
+ end
+
+ def repository_tag_digest(name, reference)
+ response = @faraday.head("/v2/#{name}/manifests/#{reference}")
+ response.headers['docker-content-digest'] if response.success?
+ end
+
+ def delete_repository_tag(name, reference)
+ @faraday.delete("/v2/#{name}/manifests/#{reference}").success?
+ end
+
+ def blob(name, digest, type = nil)
+ headers = {}
+ headers['Accept'] = type if type
+ @faraday.get("/v2/#{name}/blobs/#{digest}", nil, headers).body
+ end
+
+ def delete_blob(name, digest)
+ @faraday.delete("/v2/#{name}/blobs/#{digest}").success?
+ end
+
+ private
+
+ def initialize_connection(conn, options)
+ conn.request :json
+ conn.headers['Accept'] = MANIFEST_VERSION
+
+ conn.response :json, content_type: /\bjson$/
+
+ if options[:user] && options[:password]
+ conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
+ elsif options[:token]
+ conn.request(:authorization, :bearer, options[:token].to_s)
+ end
+
+ conn.adapter :net_http
+ end
+ end
+end
diff --git a/lib/container_registry/config.rb b/lib/container_registry/config.rb
new file mode 100644
index 00000000000..589f9f4380a
--- /dev/null
+++ b/lib/container_registry/config.rb
@@ -0,0 +1,16 @@
+module ContainerRegistry
+ class Config
+ attr_reader :tag, :blob, :data
+
+ def initialize(tag, blob)
+ @tag, @blob = tag, blob
+ @data = JSON.parse(blob.data)
+ end
+
+ def [](key)
+ return unless data
+
+ data[key]
+ end
+ end
+end
diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb
new file mode 100644
index 00000000000..0e634f6b6ef
--- /dev/null
+++ b/lib/container_registry/registry.rb
@@ -0,0 +1,21 @@
+module ContainerRegistry
+ class Registry
+ attr_reader :uri, :client, :path
+
+ def initialize(uri, options = {})
+ @uri = uri
+ @path = options[:path] || default_path
+ @client = ContainerRegistry::Client.new(uri, options)
+ end
+
+ def repository(name)
+ ContainerRegistry::Repository.new(self, name)
+ end
+
+ private
+
+ def default_path
+ @uri.sub(/^https?:\/\//, '')
+ end
+ end
+end
diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb
new file mode 100644
index 00000000000..0e4a7cb3cc9
--- /dev/null
+++ b/lib/container_registry/repository.rb
@@ -0,0 +1,48 @@
+module ContainerRegistry
+ class Repository
+ attr_reader :registry, :name
+
+ delegate :client, to: :registry
+
+ def initialize(registry, name)
+ @registry, @name = registry, name
+ end
+
+ def path
+ [registry.path, name].compact.join('/')
+ end
+
+ def tag(tag)
+ ContainerRegistry::Tag.new(self, tag)
+ end
+
+ def manifest
+ return @manifest if defined?(@manifest)
+
+ @manifest = client.repository_tags(name)
+ end
+
+ def valid?
+ manifest.present?
+ end
+
+ def tags
+ return @tags if defined?(@tags)
+ return [] unless manifest && manifest['tags']
+
+ @tags = manifest['tags'].map do |tag|
+ ContainerRegistry::Tag.new(self, tag)
+ end
+ end
+
+ def blob(config)
+ ContainerRegistry::Blob.new(self, config)
+ end
+
+ def delete_tags
+ return unless tags
+
+ tags.all?(&:delete)
+ end
+ end
+end
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
new file mode 100644
index 00000000000..43f8d6dc8c2
--- /dev/null
+++ b/lib/container_registry/tag.rb
@@ -0,0 +1,77 @@
+module ContainerRegistry
+ class Tag
+ attr_reader :repository, :name
+
+ delegate :registry, :client, to: :repository
+
+ def initialize(repository, name)
+ @repository, @name = repository, name
+ end
+
+ def valid?
+ manifest.present?
+ end
+
+ def manifest
+ return @manifest if defined?(@manifest)
+
+ @manifest = client.repository_manifest(repository.name, name)
+ end
+
+ def path
+ "#{repository.path}:#{name}"
+ end
+
+ def [](key)
+ return unless manifest
+
+ manifest[key]
+ end
+
+ def digest
+ return @digest if defined?(@digest)
+
+ @digest = client.repository_tag_digest(repository.name, name)
+ end
+
+ def config_blob
+ return @config_blob if defined?(@config_blob)
+ return unless manifest && manifest['config']
+
+ @config_blob = repository.blob(manifest['config'])
+ end
+
+ def config
+ return unless config_blob
+
+ @config ||= ContainerRegistry::Config.new(self, config_blob)
+ end
+
+ def created_at
+ return unless config
+
+ @created_at ||= DateTime.rfc3339(config['created'])
+ end
+
+ def layers
+ return @layers if defined?(@layers)
+ return unless manifest
+
+ @layers = manifest['layers'].map do |layer|
+ repository.blob(layer)
+ end
+ end
+
+ def total_size
+ return unless layers
+
+ layers.map(&:size).sum
+ end
+
+ def delete
+ return unless digest
+
+ client.delete_repository_tag(repository.name, digest)
+ end
+ end
+end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index f15b2cfd231..668d2fa41b3 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -27,7 +27,7 @@ class EventFilter
@params = if params
params.dup
else
- []#EventFilter.default_filter
+ [] # EventFilter.default_filter
end
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 7479e729db1..37f4c34054f 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -1,4 +1,4 @@
-require 'gitlab/git'
+require_dependency 'gitlab/git'
module Gitlab
def self.com?
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 132f9cd1966..3e3986d6382 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -180,7 +180,7 @@ module Gitlab
# exists?('gitlab/cookies.git')
#
def exists?(dir_name)
- File.exists?(full_path(dir_name))
+ File.exist?(full_path(dir_name))
end
protected
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
index 9b83292ef33..8d1ad62fae0 100644
--- a/lib/gitlab/bitbucket_import/client.rb
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -121,7 +121,7 @@ module Gitlab
def get(url)
response = api.get(url)
- raise Unauthorized if (400..499).include?(response.code.to_i)
+ raise Unauthorized if (400..499).cover?(response.code.to_i)
response
end
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
index 941f818b847..b90ef0b0fba 100644
--- a/lib/gitlab/bitbucket_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(
+ ::Projects::CreateService.new(
current_user,
name: repo["name"],
path: repo["slug"],
@@ -21,11 +21,8 @@ module Gitlab
import_type: "bitbucket",
import_source: "#{repo["owner"]}/#{repo["slug"]}",
import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git",
+ import_data: { credentials: { bb_session: session_data } }
).execute
-
- project.create_or_update_import_data(credentials: { bb_session: session_data })
-
- project
end
end
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index f2020c82d40..cd2e83b4c27 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -56,7 +56,7 @@ module Gitlab
child_pattern = '[^/]*/?$' unless @opts[:recursive]
match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/
- until gz.eof? do
+ until gz.eof?
begin
path = read_string(gz).force_encoding('UTF-8')
meta = read_string(gz).force_encoding('UTF-8')
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 85583dce9ee..9dc2602867e 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -19,7 +19,7 @@ module Gitlab
select('date(created_at) as date, count(id) as total_amount').
map(&:attributes)
- dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a
+ dates = (1.year.ago.to_date..Date.today).to_a
dates.each do |date|
date_id = date.to_time.to_i.to_s
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index f44d1b3a44e..92c7e8b9d88 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -1,18 +1,22 @@
module Gitlab
module CurrentSettings
def current_application_settings
- key = :current_application_settings
-
- RequestStore.store[key] ||= begin
- settings = nil
+ if RequestStore.active?
+ RequestStore.fetch(:current_application_settings) { ensure_application_settings! }
+ else
+ ensure_application_settings!
+ end
+ end
- if connect_to_db?
- settings = ::ApplicationSetting.current
- settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
- end
+ def ensure_application_settings!
+ settings = ::ApplicationSetting.cached
- settings || fake_application_settings
+ if !settings && connect_to_db?
+ settings = ::ApplicationSetting.current
+ settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end
+
+ settings || fake_application_settings
end
def fake_application_settings
@@ -36,6 +40,7 @@ module Gitlab
two_factor_grace_period: 48,
akismet_enabled: false,
repository_checks_enabled: true,
+ container_registry_token_expire_delay: 5,
)
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 6f9da69983a..42bec913a45 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -5,11 +5,11 @@ module Gitlab
end
def self.mysql?
- adapter_name.downcase == 'mysql2'
+ adapter_name.casecmp('mysql2').zero?
end
def self.postgresql?
- adapter_name.downcase == 'postgresql'
+ adapter_name.casecmp('postgresql').zero?
end
def self.version
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
new file mode 100644
index 00000000000..fd14234c558
--- /dev/null
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -0,0 +1,142 @@
+module Gitlab
+ module Database
+ module MigrationHelpers
+ # Creates a new index, concurrently when supported
+ #
+ # On PostgreSQL this method creates an index concurrently, on MySQL this
+ # creates a regular index.
+ #
+ # Example:
+ #
+ # add_concurrent_index :users, :some_column
+ #
+ # See Rails' `add_index` for more info on the available arguments.
+ def add_concurrent_index(*args)
+ if transaction_open?
+ raise 'add_concurrent_index can not be run inside a transaction, ' \
+ 'you can disable transactions by calling disable_ddl_transaction! ' \
+ 'in the body of your migration class'
+ end
+
+ if Database.postgresql?
+ args << { algorithm: :concurrently }
+ end
+
+ add_index(*args)
+ end
+
+ # Updates the value of a column in batches.
+ #
+ # This method updates the table in batches of 5% of the total row count.
+ # Any data inserted while running this method (or after it has finished
+ # running) is _not_ updated automatically.
+ #
+ # This method _only_ updates rows where the column's value is set to NULL.
+ #
+ # table - The name of the table.
+ # column - The name of the column to update.
+ # value - The value for the column.
+ def update_column_in_batches(table, column, value)
+ quoted_table = quote_table_name(table)
+ quoted_column = quote_column_name(column)
+
+ ##
+ # Workaround for #17711
+ #
+ # It looks like for MySQL `ActiveRecord::Base.conntection.quote(true)`
+ # returns correct value (1), but `ActiveRecord::Migration.new.quote`
+ # returns incorrect value ('true'), which causes migrations to fail.
+ #
+ quoted_value = connection.quote(value)
+ processed = 0
+
+ total = exec_query("SELECT COUNT(*) AS count FROM #{quoted_table}").
+ to_hash.
+ first['count'].
+ to_i
+
+ # Update in batches of 5%
+ batch_size = ((total / 100.0) * 5.0).ceil
+
+ while processed < total
+ start_row = exec_query(%Q{
+ SELECT id
+ FROM #{quoted_table}
+ ORDER BY id ASC
+ LIMIT 1 OFFSET #{processed}
+ }).to_hash.first
+
+ stop_row = exec_query(%Q{
+ SELECT id
+ FROM #{quoted_table}
+ ORDER BY id ASC
+ LIMIT 1 OFFSET #{processed + batch_size}
+ }).to_hash.first
+
+ query = %Q{
+ UPDATE #{quoted_table}
+ SET #{quoted_column} = #{quoted_value}
+ WHERE id >= #{start_row['id']}
+ }
+
+ if stop_row
+ query += " AND id < #{stop_row['id']}"
+ end
+
+ execute(query)
+
+ processed += batch_size
+ end
+ end
+
+ # Adds a column with a default value without locking an entire table.
+ #
+ # This method runs the following steps:
+ #
+ # 1. Add the column with a default value of NULL.
+ # 2. Update all existing rows in batches.
+ # 3. Change the default value of the column to the specified value.
+ # 4. Update any remaining rows.
+ #
+ # These steps ensure a column can be added to a large and commonly used
+ # table without locking the entire table for the duration of the table
+ # modification.
+ #
+ # table - The name of the table to update.
+ # column - The name of the column to add.
+ # type - The column type (e.g. `:integer`).
+ # default - The default value for the column.
+ # allow_null - When set to `true` the column will allow NULL values, the
+ # default is to not allow NULL values.
+ def add_column_with_default(table, column, type, default:, allow_null: false)
+ if transaction_open?
+ raise 'add_column_with_default can not be run inside a transaction, ' \
+ 'you can disable transactions by calling disable_ddl_transaction! ' \
+ 'in the body of your migration class'
+ end
+
+ transaction do
+ add_column(table, column, type, default: nil)
+
+ # Changing the default before the update ensures any newly inserted
+ # rows already use the proper default value.
+ change_column_default(table, column, default)
+ end
+
+ begin
+ transaction do
+ update_column_in_batches(table, column, default)
+ end
+ # We want to rescue _all_ exceptions here, even those that don't inherit
+ # from StandardError.
+ rescue Exception => error # rubocop: disable all
+ remove_column(table, column)
+
+ raise error
+ end
+
+ change_column_null(table, column, false) unless allow_null
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index dccb717e95d..87a9b1e23ac 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -1,6 +1,11 @@
module Gitlab
module Diff
class InlineDiffMarker
+ MARKDOWN_SYMBOLS = {
+ addition: "+",
+ deletion: "-"
+ }
+
attr_accessor :raw_line, :rich_line
def initialize(raw_line, rich_line = raw_line)
@@ -8,7 +13,7 @@ module Gitlab
@rich_line = ERB::Util.html_escape(rich_line)
end
- def mark(line_inline_diffs)
+ def mark(line_inline_diffs, mode: nil, markdown: false)
return rich_line unless line_inline_diffs
marker_ranges = []
@@ -20,13 +25,22 @@ module Gitlab
end
offset = 0
- # Mark each range
- marker_ranges.each_with_index do |range, i|
- class_names = ["idiff"]
- class_names << "left" if i == 0
- class_names << "right" if i == marker_ranges.length - 1
- offset = insert_around_range(rich_line, range, "<span class='#{class_names.join(" ")}'>", "</span>", offset)
+ # Mark each range
+ marker_ranges.each_with_index do |range, index|
+ before_content =
+ if markdown
+ "{#{MARKDOWN_SYMBOLS[mode]}"
+ else
+ "<span class='#{html_class_names(marker_ranges, mode, index)}'>"
+ end
+ after_content =
+ if markdown
+ "#{MARKDOWN_SYMBOLS[mode]}}"
+ else
+ "</span>"
+ end
+ offset = insert_around_range(rich_line, range, before_content, after_content, offset)
end
rich_line.html_safe
@@ -34,6 +48,14 @@ module Gitlab
private
+ def html_class_names(marker_ranges, mode, index)
+ class_names = ["idiff"]
+ class_names << "left" if index == 0
+ class_names << "right" if index == marker_ranges.length - 1
+ class_names << mode if mode
+ class_names.join(" ")
+ end
+
# Mapping of character positions in the raw line, to the rich (highlighted) line
def position_mapping
@position_mapping ||= begin
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index d0815fc7eea..522dd2b9428 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -17,16 +17,16 @@ module Gitlab
Enumerator.new do |yielder|
@lines.each do |line|
next if filename?(line)
-
- full_line = line.gsub(/\n/, '')
-
+
+ full_line = line.delete("\n")
+
if line.match(/^@@ -/)
type = "match"
-
+
line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
-
- next if line_old <= 1 && line_new <= 1 #top of file
+
+ next if line_old <= 1 && line_new <= 1 # top of file
yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1
next
@@ -39,8 +39,8 @@ module Gitlab
yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1
end
-
-
+
+
case line[0]
when "+"
line_new += 1
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 8f9be6cd9a3..e2fee6b9f3e 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -2,22 +2,21 @@ module Gitlab
module Email
module Message
class RepositoryPush
- attr_accessor :recipient
attr_reader :author_id, :ref, :action
include Gitlab::Routing.url_helpers
+ include DiffHelper
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author
delegate :username, to: :author, prefix: :author
- def initialize(notify, project_id, recipient, opts = {})
+ def initialize(notify, project_id, opts = {})
raise ArgumentError, 'Missing options: author_id, ref, action' unless
opts[:author_id] && opts[:ref] && opts[:action]
@notify = notify
@project_id = project_id
- @recipient = recipient
@opts = opts.dup
@author_id = @opts.delete(:author_id)
@@ -38,7 +37,7 @@ module Gitlab
end
def diffs
- @diffs ||= (compare.diffs if compare)
+ @diffs ||= (safe_diff_files(compare.diffs, diff_refs) if compare)
end
def diffs_count
@@ -49,6 +48,10 @@ module Gitlab
@opts[:compare]
end
+ def diff_refs
+ @opts[:diff_refs]
+ end
+
def compare_timeout
diffs.overflow? if diffs
end
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index 6ed36b51f12..3411eb1d9ce 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -65,7 +65,7 @@ module Gitlab
(l =~ /On \w+ \d+,? \d+,?.*wrote:/)
# Headers on subsequent lines
- break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX }
+ break if (0..2).all? { |off| lines[idx + off] =~ REPLYING_HEADER_REGEX }
# Headers on the same line
break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index 3840765db87..1918d5b208d 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(
+ ::Projects::CreateService.new(
current_user,
name: repo.safe_name,
path: repo.path,
@@ -21,12 +21,9 @@ module Gitlab
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
import_type: 'fogbugz',
import_source: repo.name,
- import_url: Project::UNKNOWN_IMPORT_URL
+ import_url: Project::UNKNOWN_IMPORT_URL,
+ import_data: { data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session } }
).execute
-
- project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session })
-
- project
end
end
end
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
new file mode 100644
index 00000000000..a15fc84b418
--- /dev/null
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module GithubImport
+ class BranchFormatter < BaseFormatter
+ delegate :repo, :sha, :ref, to: :raw_data
+
+ def exists?
+ project.repository.branch_exists?(ref)
+ end
+
+ def name
+ @name ||= exists? ? ref : "#{ref}-#{short_id}"
+ end
+
+ def valid?
+ repo.present?
+ end
+
+ def valid?
+ repo.present?
+ end
+
+ private
+
+ def short_id
+ sha.to_s[0..7]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 0f9e3ee14ee..408d9b79632 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -3,12 +3,15 @@ module Gitlab
class Importer
include Gitlab::ShellAdapter
- attr_reader :project, :client
+ attr_reader :client, :project, :repo, :repo_url
def initialize(project)
- @project = project
- if import_data_credentials
- @client = Client.new(import_data_credentials[:user])
+ @project = project
+ @repo = project.import_source
+ @repo_url = project.import_url
+
+ if credentials
+ @client = Client.new(credentials[:user])
@formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
@@ -22,12 +25,12 @@ module Gitlab
private
- def import_data_credentials
- @import_data_credentials ||= project.import_data.credentials if project.import_data
+ def credentials
+ @credentials ||= project.import_data.credentials if project.import_data
end
def import_labels
- client.labels(project.import_source).each do |raw_data|
+ client.labels(repo).each do |raw_data|
Label.create!(LabelFormatter.new(project, raw_data).attributes)
end
@@ -37,7 +40,7 @@ module Gitlab
end
def import_milestones
- client.list_milestones(project.import_source, state: :all).each do |raw_data|
+ client.list_milestones(repo, state: :all).each do |raw_data|
Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
end
@@ -47,9 +50,7 @@ module Gitlab
end
def import_issues
- client.list_issues(project.import_source, state: :all,
- sort: :created,
- direction: :asc).each do |raw_data|
+ client.list_issues(repo, state: :all, sort: :created, direction: :asc).each do |raw_data|
gh_issue = IssueFormatter.new(project, raw_data)
if gh_issue.valid?
@@ -68,29 +69,50 @@ module Gitlab
end
def import_pull_requests
- client.pull_requests(project.import_source, state: :all,
- sort: :created,
- direction: :asc).each do |raw_data|
- pull_request = PullRequestFormatter.new(project, raw_data)
-
- if pull_request.valid?
- merge_request = MergeRequest.new(pull_request.attributes)
-
- if merge_request.save
- apply_labels(pull_request.number, merge_request)
- import_comments(pull_request.number, merge_request)
- import_comments_on_diff(pull_request.number, merge_request)
- end
+ pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc)
+ .map { |raw| PullRequestFormatter.new(project, raw) }
+ .select(&:valid?)
+
+ source_branches_removed = pull_requests.reject(&:source_branch_exists?).map { |pr| [pr.source_branch_name, pr.source_branch_sha] }
+ target_branches_removed = pull_requests.reject(&:target_branch_exists?).map { |pr| [pr.target_branch_name, pr.target_branch_sha] }
+ branches_removed = source_branches_removed | target_branches_removed
+
+ create_refs(branches_removed)
+
+ pull_requests.each do |pull_request|
+ merge_request = MergeRequest.new(pull_request.attributes)
+
+ if merge_request.save
+ apply_labels(pull_request.number, merge_request)
+ import_comments(pull_request.number, merge_request)
+ import_comments_on_diff(pull_request.number, merge_request)
end
end
+ delete_refs(branches_removed)
+
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
end
+ def create_refs(branches)
+ branches.each do |name, sha|
+ client.create_ref(repo, "refs/heads/#{name}", sha)
+ end
+
+ project.repository.fetch_ref(repo_url, '+refs/heads/*', 'refs/heads/*')
+ end
+
+ def delete_refs(branches)
+ branches.each do |name, _|
+ client.delete_ref(repo, "heads/#{name}")
+ project.repository.rm_branch(project.creator, name)
+ end
+ end
+
def apply_labels(number, issuable)
- issue = client.issue(project.import_source, number)
+ issue = client.issue(repo, number)
if issue.labels.count > 0
label_ids = issue.labels.map do |raw|
@@ -102,12 +124,12 @@ module Gitlab
end
def import_comments(issue_number, noteable)
- comments = client.issue_comments(project.import_source, issue_number)
+ comments = client.issue_comments(repo, issue_number)
create_comments(comments, noteable)
end
def import_comments_on_diff(pull_request_number, merge_request)
- comments = client.pull_request_comments(project.import_source, pull_request_number)
+ comments = client.pull_request_comments(repo, pull_request_number)
create_comments(comments, merge_request)
end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index d21b942ad4b..a2947b56ad9 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -1,15 +1,20 @@
module Gitlab
module GithubImport
class PullRequestFormatter < BaseFormatter
+ delegate :exists?, :name, :project, :repo, :sha, to: :source_branch, prefix: true
+ delegate :exists?, :name, :project, :repo, :sha, to: :target_branch, prefix: true
+
def attributes
{
iid: number,
title: raw_data.title,
description: description,
- source_project: source_project,
- source_branch: source_branch.name,
- target_project: target_project,
- target_branch: target_branch.name,
+ source_project: source_branch_project,
+ source_branch: source_branch_name,
+ head_source_sha: source_branch_sha,
+ target_project: target_branch_project,
+ target_branch: target_branch_name,
+ base_target_sha: target_branch_sha,
state: state,
milestone: milestone,
author_id: author_id,
@@ -24,7 +29,15 @@ module Gitlab
end
def valid?
- !cross_project? && source_branch.present? && target_branch.present?
+ source_branch.valid? && target_branch.valid? && !cross_project?
+ end
+
+ def source_branch
+ @source_branch ||= BranchFormatter.new(project, raw_data.head)
+ end
+
+ def target_branch
+ @target_branch ||= BranchFormatter.new(project, raw_data.base)
end
private
@@ -52,7 +65,7 @@ module Gitlab
end
def cross_project?
- source_repo.present? && target_repo.present? && source_repo.id != target_repo.id
+ source_branch_repo.id != target_branch_repo.id
end
def description
@@ -65,35 +78,10 @@ module Gitlab
end
end
- def source_project
- project
- end
-
- def source_repo
- raw_data.head.repo
- end
-
- def source_branch
- source_project.repository.find_branch(raw_data.head.ref)
- end
-
- def target_project
- project
- end
-
- def target_repo
- raw_data.base.repo
- end
-
- def target_branch
- target_project.repository.find_branch(raw_data.base.ref)
- end
-
def state
- @state ||= case true
- when raw_data.state == 'closed' && raw_data.merged_at.present?
+ @state ||= if raw_data.state == 'closed' && raw_data.merged_at.present?
'merged'
- when raw_data.state == 'closed'
+ elsif raw_data.state == 'closed'
'closed'
else
'opened'
diff --git a/lib/gitlab/gitignore.rb b/lib/gitlab/gitignore.rb
new file mode 100644
index 00000000000..f46b43b61a4
--- /dev/null
+++ b/lib/gitlab/gitignore.rb
@@ -0,0 +1,56 @@
+module Gitlab
+ class Gitignore
+ FILTER_REGEX = /\.gitignore\z/.freeze
+
+ def initialize(path)
+ @path = path
+ end
+
+ def name
+ File.basename(@path, '.gitignore')
+ end
+
+ def content
+ File.read(@path)
+ end
+
+ class << self
+ def all
+ languages_frameworks + global
+ end
+
+ def find(key)
+ file_name = "#{key}.gitignore"
+
+ directory = select_directory(file_name)
+ directory ? new(File.join(directory, file_name)) : nil
+ end
+
+ def global
+ files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) }
+ end
+
+ def languages_frameworks
+ files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) }
+ end
+
+ private
+
+ def select_directory(file_name)
+ [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) }
+ end
+
+ def global_dir
+ File.join(gitignore_dir, 'Global')
+ end
+
+ def gitignore_dir
+ Rails.root.join('vendor/gitignore')
+ end
+
+ def files_for_folder(dir)
+ Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 96717b42bae..3f76ec97977 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -5,9 +5,9 @@ module Gitlab
def initialize(project)
@project = project
- credentials = import_data
- if credentials && credentials[:password]
- @client = Client.new(credentials[:password])
+ import_data = project.import_data
+ if import_data && import_data.credentials && import_data.credentials[:password]
+ @client = Client.new(import_data.credentials[:password])
@formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
@@ -17,7 +17,7 @@ module Gitlab
def execute
project_identifier = CGI.escape(project.import_source)
- #Issues && Comments
+ # Issues && Comments
issues = client.issues(project_identifier)
issues.each do |issue|
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
index 0abb7a64c17..326cfcaa8af 100644
--- a/lib/gitlab/google_code_import/project_creator.rb
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(
+ ::Projects::CreateService.new(
current_user,
name: repo.name,
path: repo.name,
@@ -21,12 +21,9 @@ module Gitlab
visibility_level: Gitlab::VisibilityLevel::PUBLIC,
import_type: "google_code",
import_source: repo.name,
- import_url: repo.import_url
+ import_url: repo.import_url,
+ import_data: { data: { 'repo' => repo.raw_data, 'user_map' => user_map } }
).execute
-
- project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map })
-
- project
end
end
end
diff --git a/lib/gitlab/lazy.rb b/lib/gitlab/lazy.rb
new file mode 100644
index 00000000000..2a659ae4c74
--- /dev/null
+++ b/lib/gitlab/lazy.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ # A class that can be wrapped around an expensive method call so it's only
+ # executed when actually needed.
+ #
+ # Usage:
+ #
+ # object = Gitlab::Lazy.new { some_expensive_work_here }
+ #
+ # object['foo']
+ # object.bar
+ class Lazy < BasicObject
+ def initialize(&block)
+ @block = block
+ end
+
+ def method_missing(name, *args, &block)
+ __evaluate__
+
+ @result.__send__(name, *args, &block)
+ end
+
+ def respond_to_missing?(name, include_private = false)
+ __evaluate__
+
+ @result.respond_to?(name, include_private) || super
+ end
+
+ private
+
+ def __evaluate__
+ @result = @block.call unless defined?(@result)
+ end
+ end
+end
diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb
index a5f767b134d..dda371e6554 100644
--- a/lib/gitlab/markup_helper.rb
+++ b/lib/gitlab/markup_helper.rb
@@ -40,7 +40,7 @@ module Gitlab
# Returns boolean
def plain?(filename)
filename.downcase.end_with?('.txt') ||
- filename.downcase == 'readme'
+ filename.casecmp('readme').zero?
end
def previewable?(filename)
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index 708ef79f304..0f115893a15 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -154,8 +154,6 @@ module Gitlab
duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold
- trans.increment(:method_duration, duration)
-
trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
{ duration: duration },
method: #{label.inspect})
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 49e5f86e6e6..8e345e8ae4a 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -6,26 +6,28 @@ module Gitlab
attach_to :active_support
def cache_read(event)
- increment(:cache_read_duration, event.duration)
+ increment(:cache_read, event.duration)
end
def cache_write(event)
- increment(:cache_write_duration, event.duration)
+ increment(:cache_write, event.duration)
end
def cache_delete(event)
- increment(:cache_delete_duration, event.duration)
+ increment(:cache_delete, event.duration)
end
def cache_exist?(event)
- increment(:cache_exists_duration, event.duration)
+ increment(:cache_exists, event.duration)
end
def increment(key, duration)
return unless current_transaction
current_transaction.increment(:cache_duration, duration)
- current_transaction.increment(key, duration)
+ current_transaction.increment(:cache_count, 1)
+ current_transaction.increment("#{key}_duration".to_sym, duration)
+ current_transaction.increment("#{key}_count".to_sym, 1)
end
private
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index 50b0dd32380..5764ab15652 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -39,7 +39,7 @@ module Gitlab
request_url = URI.join(base_url, project_path)
domain_path = strip_url(request_url.to_s)
- "<!DOCTYPE html><html><head><meta content='#{domain_path} git #{request_url}.git' name='go-import'></head></html>\n";
+ "<!DOCTYPE html><html><head><meta content='#{domain_path} git #{request_url}.git' name='go-import'></head></html>\n"
end
def strip_url(url)
diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb
new file mode 100644
index 00000000000..56608b1b276
--- /dev/null
+++ b/lib/gitlab/middleware/rails_queue_duration.rb
@@ -0,0 +1,24 @@
+# This Rack middleware is intended to measure the latency between
+# gitlab-workhorse forwarding a request to the Rails application and the
+# time this middleware is reached.
+
+module Gitlab
+ module Middleware
+ class RailsQueueDuration
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ trans = Gitlab::Metrics.current_transaction
+ proxy_start = env['HTTP_GITLAB_WORHORSE_PROXY_START'].presence
+ if trans && proxy_start
+ # Time in milliseconds since gitlab-workhorse started the request
+ trans.set(:rails_queue_duration, Time.now.to_f * 1_000 - proxy_start.to_f / 1_000_000)
+ end
+
+ @app.call(env)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 71c5b6801fb..183bd10d6a3 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -74,7 +74,7 @@ module Gitlab
end
def notes
- project.notes.user.search(query).order('updated_at DESC')
+ project.notes.user.search(query, as_user: @current_user).order('updated_at DESC')
end
def commits
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 5c352c96de5..40766f35f77 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -25,7 +25,7 @@ module Gitlab
end
@pool.with { |redis| yield redis }
end
-
+
def self.redis_store_options
url = new.url
redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url)
@@ -40,10 +40,10 @@ module Gitlab
def initialize(rails_env=nil)
rails_env ||= Rails.env
config_file = File.expand_path('../../../config/resque.yml', __FILE__)
-
+
@url = "redis://localhost:6379"
- if File.exists?(config_file)
- @url =YAML.load_file(config_file)[rails_env]
+ if File.exist?(config_file)
+ @url = YAML.load_file(config_file)[rails_env]
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 13c4d64c99b..11c0b01f0dc 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -4,10 +4,9 @@ module Gitlab
REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range)
attr_accessor :project, :current_user, :author
- def initialize(project, current_user = nil, author = nil)
+ def initialize(project, current_user = nil)
@project = project
@current_user = current_user
- @author = author
@references = {}
@@ -18,17 +17,21 @@ module Gitlab
super(text, context.merge(project: project))
end
+ def references(type)
+ super(type, project, current_user)
+ end
+
REFERABLES.each do |type|
define_method("#{type}s") do
- @references[type] ||= references(type, reference_context)
+ @references[type] ||= references(type)
end
end
def issues
if project && project.jira_tracker?
- @references[:external_issue] ||= references(:external_issue, reference_context)
+ @references[:external_issue] ||= references(:external_issue)
else
- @references[:issue] ||= references(:issue, reference_context)
+ @references[:issue] ||= references(:issue)
end
end
@@ -46,11 +49,5 @@ module Gitlab
@pattern = Regexp.union(patterns.compact)
end
-
- private
-
- def reference_context
- { project: project, current_user: current_user, author: author }
- end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index ace906a6f59..1cbd6d945a0 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -96,5 +96,9 @@ module Gitlab
(?<![\/.]) (?# rule #6-7)
}x.freeze
end
+
+ def container_registry_reference_regex
+ git_reference_regex
+ end
end
end
diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb
index b98589dff89..5e95f6c0529 100644
--- a/lib/gitlab/sanitizers/svg.rb
+++ b/lib/gitlab/sanitizers/svg.rb
@@ -1,5 +1,3 @@
-require_relative "svg/whitelist"
-
module Gitlab
module Sanitizers
module SVG
@@ -12,14 +10,14 @@ module Gitlab
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)
+ unless Whitelist::ALLOWED_ELEMENTS.include?(node.name)
node.unlink
else
node.attributes.each do |attr_name, attr|
- valid_attributes = ALLOWED_ATTRIBUTES[node.name]
+ valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name]
unless valid_attributes && valid_attributes.include?(attr_name)
- if ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) &&
+ if Whitelist::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.
diff --git a/lib/gitlab/sanitizers/svg/whitelist.rb b/lib/gitlab/sanitizers/svg/whitelist.rb
index 917e795b29e..7b6b70d8dbc 100644
--- a/lib/gitlab/sanitizers/svg/whitelist.rb
+++ b/lib/gitlab/sanitizers/svg/whitelist.rb
@@ -4,7 +4,8 @@
module Gitlab
module Sanitizers
module SVG
- ALLOWED_ELEMENTS = %w[
+ class Whitelist
+ ALLOWED_ELEMENTS = %w[
a altGlyph altGlyphDef altGlyphItem animate
animateColor animateMotion animateTransform circle clipPath color-profile
cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer
@@ -18,90 +19,91 @@ module Gitlab
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_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
+ 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
end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 2bbbd3074e8..fe65c246101 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def wiki_page_url
- "#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}"
+ namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
end
end
end
diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/url_sanitizer.rb
index d23b013c1f5..7d02fe3c971 100644
--- a/lib/gitlab/import_url.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -1,7 +1,13 @@
module Gitlab
- class ImportUrl
+ class UrlSanitizer
+ def self.sanitize(content)
+ regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git'])
+
+ content.gsub(regexp) { |url| new(url).masked_url }
+ end
+
def initialize(url, credentials: nil)
- @url = URI.parse(URI.encode(url))
+ @url = Addressable::URI.parse(url)
@credentials = credentials
end
@@ -9,6 +15,13 @@ module Gitlab
@sanitized_url ||= safe_url.to_s
end
+ def masked_url
+ url = @url.dup
+ url.password = "*****" unless url.password.nil?
+ url.user = "*****" unless url.user.nil?
+ url.to_s
+ end
+
def credentials
@credentials ||= { user: @url.user, password: @url.password }
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index a1ee1cba216..9462f3368e6 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -32,6 +32,13 @@ module Gitlab
}
end
+ def highest_allowed_level
+ restricted_levels = current_application_settings.restricted_visibility_levels
+
+ allowed_levels = self.values - restricted_levels
+ allowed_levels.max || PRIVATE
+ end
+
def allowed_for?(user, level)
user.is_admin? || allowed_level?(level.to_i)
end
diff --git a/lib/json_web_token/rsa_token.rb b/lib/json_web_token/rsa_token.rb
new file mode 100644
index 00000000000..d6d6af7089c
--- /dev/null
+++ b/lib/json_web_token/rsa_token.rb
@@ -0,0 +1,42 @@
+module JSONWebToken
+ class RSAToken < Token
+ attr_reader :key_file
+
+ def initialize(key_file)
+ super()
+ @key_file = key_file
+ end
+
+ def encoded
+ headers = {
+ kid: kid
+ }
+ JWT.encode(payload, key, 'RS256', headers)
+ end
+
+ private
+
+ def key_data
+ @key_data ||= File.read(key_file)
+ end
+
+ def key
+ @key ||= OpenSSL::PKey::RSA.new(key_data)
+ end
+
+ def public_key
+ key.public_key
+ end
+
+ def kid
+ # calculate sha256 from DER encoded ASN1
+ kid = Digest::SHA256.digest(public_key.to_der)
+
+ # we encode only 30 bytes with base32
+ kid = Base32.encode(kid[0..29])
+
+ # insert colon every 4 characters
+ kid.scan(/.{4}/).join(':')
+ end
+ end
+end
diff --git a/lib/json_web_token/token.rb b/lib/json_web_token/token.rb
new file mode 100644
index 00000000000..5b67715b0b2
--- /dev/null
+++ b/lib/json_web_token/token.rb
@@ -0,0 +1,46 @@
+module JSONWebToken
+ class Token
+ attr_accessor :issuer, :subject, :audience, :id
+ attr_accessor :issued_at, :not_before, :expire_time
+
+ def initialize
+ @id = SecureRandom.uuid
+ @issued_at = Time.now
+ # we give a few seconds for time shift
+ @not_before = issued_at - 5.seconds
+ # default 60 seconds should be more than enough for this authentication token
+ @expire_time = issued_at + 1.minute
+ @custom_payload = {}
+ end
+
+ def [](key)
+ @custom_payload[key]
+ end
+
+ def []=(key, value)
+ @custom_payload[key] = value
+ end
+
+ def encoded
+ raise NotImplementedError
+ end
+
+ def payload
+ @custom_payload.merge(default_payload)
+ end
+
+ private
+
+ def default_payload
+ {
+ jti: id,
+ aud: audience,
+ sub: subject,
+ iss: issuer,
+ iat: issued_at.to_i,
+ nbf: not_before.to_i,
+ exp: expire_time.to_i
+ }.compact
+ end
+ end
+end
diff --git a/lib/support/nginx/registry-ssl b/lib/support/nginx/registry-ssl
new file mode 100644
index 00000000000..92511e26861
--- /dev/null
+++ b/lib/support/nginx/registry-ssl
@@ -0,0 +1,53 @@
+## Lines starting with two hashes (##) are comments with information.
+## Lines starting with one hash (#) are configuration parameters that can be uncommented.
+##
+###################################
+## configuration ##
+###################################
+
+## Redirects all HTTP traffic to the HTTPS host
+server {
+ listen *:80;
+ server_name registry.gitlab.example.com;
+ server_tokens off; ## Don't show the nginx version number, a security best practice
+ return 301 https://$http_host:$request_uri;
+ access_log /var/log/nginx/gitlab_registry_access.log gitlab_access;
+ error_log /var/log/nginx/gitlab_registry_error.log;
+}
+
+server {
+ # If a different port is specified in https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/config/gitlab.yml.example#L182,
+ # it should be declared here as well
+ listen *:443 ssl http2;
+ server_name registry.gitlab.example.com;
+ server_tokens off; ## Don't show the nginx version number, a security best practice
+
+ client_max_body_size 0;
+ chunked_transfer_encoding on;
+
+ ## Strong SSL Security
+ ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
+ ssl on;
+ ssl_certificate /etc/gitlab/ssl/registry.gitlab.example.com.crt
+ ssl_certificate_key /etc/gitlab/ssl/registry.gitlab.example.com.key
+
+ ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_prefer_server_ciphers on;
+ ssl_session_cache builtin:1000 shared:SSL:10m;
+ ssl_session_timeout 5m;
+
+ access_log /var/log/gitlab/nginx/gitlab_registry_access.log gitlab_access;
+ error_log /var/log/gitlab/nginx/gitlab_registry_error.log;
+
+ location / {
+ proxy_set_header Host $http_host; # required for docker client's sake
+ proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_read_timeout 900;
+
+ proxy_pass http://localhost:5000;
+ }
+
+}
diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake
deleted file mode 100644
index 16bad4bd2bd..00000000000
--- a/lib/tasks/auto_annotate_models.rake
+++ /dev/null
@@ -1,44 +0,0 @@
-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/backup.rake b/lib/tasks/gitlab/backup.rake
index 402bb338f27..596eaca6d0d 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -14,6 +14,7 @@ namespace :gitlab do
Rake::Task["gitlab:backup:builds:create"].invoke
Rake::Task["gitlab:backup:artifacts:create"].invoke
Rake::Task["gitlab:backup:lfs:create"].invoke
+ Rake::Task["gitlab:backup:registry:create"].invoke
backup = Backup::Manager.new
backup.pack
@@ -54,6 +55,7 @@ namespace :gitlab do
Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds')
Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts')
Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs')
+ Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry')
Rake::Task['gitlab:shell:setup'].invoke
backup.cleanup
@@ -173,6 +175,33 @@ namespace :gitlab do
end
end
+ namespace :registry do
+ task create: :environment do
+ $progress.puts "Dumping container registry images ... ".blue
+
+ if Gitlab.config.registry.enabled
+ if ENV["SKIP"] && ENV["SKIP"].include?("registry")
+ $progress.puts "[SKIPPED]".cyan
+ else
+ Backup::Registry.new.dump
+ $progress.puts "done".green
+ end
+ else
+ $progress.puts "[DISABLED]".cyan
+ end
+ end
+
+ task restore: :environment do
+ $progress.puts "Restoring container registry images ... ".blue
+ if Gitlab.config.registry.enabled
+ Backup::Registry.new.restore
+ $progress.puts "done".green
+ else
+ $progress.puts "[DISABLED]".cyan
+ end
+ end
+ end
+
def configure_cron_mode
if ENV['CRON']
# We need an object we can say 'puts' and 'print' to; let's use a
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index effb8eb6001..fad89c73762 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -303,7 +303,7 @@ namespace :gitlab do
else
puts "no".red
try_fixing_it(
- "sudo find #{upload_path} -type d -not -path #{upload_path} -exec chmod 0700 {} \\;"
+ "sudo chmod 700 #{upload_path}"
)
for_more_information(
see_installation_guide_section "GitLab"
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 1c706dc11b3..86f5d65f128 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -29,10 +29,22 @@ namespace :gitlab do
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
connection.execute('TRUNCATE schema_migrations')
+
# 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") }
+ # Add `IF EXISTS` because cascade could have already deleted a table.
+ tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{t} CASCADE") }
+ end
+
+ desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
+ task configure: :environment do
+ if ActiveRecord::Base.connection.tables.any?
+ Rake::Task['db:migrate'].invoke
+ else
+ Rake::Task['db:schema:load'].invoke
+ Rake::Task['db:seed_fu'].invoke
+ end
end
end
end
diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake
new file mode 100644
index 00000000000..84aa312002b
--- /dev/null
+++ b/lib/tasks/gitlab/update_gitignore.rake
@@ -0,0 +1,46 @@
+namespace :gitlab do
+ desc "GitLab | Update gitignore"
+ task :update_gitignore do
+ unless clone_gitignores
+ puts "Cloning the gitignores failed".red
+ return
+ end
+
+ remove_unneeded_files(gitignore_directory)
+ remove_unneeded_files(global_directory)
+
+ puts "Done".green
+ end
+
+ def clone_gitignores
+ FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory)
+ FileUtils.cd vendor_directory
+
+ system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git')
+ end
+
+ # Retain only certain files:
+ # - The LICENSE, because we have to
+ # - The sub dir global
+ # - The gitignores themself
+ # - Dir.entires returns also the entries '.' and '..'
+ def remove_unneeded_files(path)
+ Dir.foreach(path) do |file|
+ FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
+ end
+ end
+
+ private
+
+ def vendor_directory
+ Rails.root.join('vendor')
+ end
+
+ def gitignore_directory
+ File.join(vendor_directory, 'gitignore')
+ end
+
+ def global_directory
+ File.join(gitignore_directory, 'Global')
+ end
+end
diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake
index ddfaf5d51f2..78ffccc9d06 100644
--- a/lib/tasks/rubocop.rake
+++ b/lib/tasks/rubocop.rake
@@ -1,4 +1,5 @@
unless Rails.env.production?
require 'rubocop/rake_task'
+
RuboCop::RakeTask.new
end
diff --git a/public/robots.txt b/public/robots.txt
index 4f616c7f4c1..334f4c03533 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -65,3 +65,4 @@ Disallow: /*/*/deploy_keys
Disallow: /*/*/hooks
Disallow: /*/*/services
Disallow: /*/*/protected_branches
+Disallow: /*/*/uploads/
diff --git a/shared/registry/.gitkeep b/shared/registry/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/shared/registry/.gitkeep
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 462afb24f08..6fad7e2b9e7 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -43,7 +43,7 @@ describe "mail_room.yml" do
redis_config_file = Rails.root.join('config', 'resque.yml')
redis_url =
- if File.exists?(redis_config_file)
+ if File.exist?(redis_config_file)
YAML.load_file(redis_config_file)[Rails.env]
else
"redis://localhost:6379"
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 2ba0d489197..4cb8b8da150 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -17,7 +17,7 @@ describe Admin::ProjectsController do
it 'does not retrieve the project' do
get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]
- expect(response.body).to_not match(project.name)
+ expect(response.body).not_to match(project.name)
end
end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index ce2a62ae1fd..6caf37ddc2c 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -114,6 +114,82 @@ describe Admin::UsersController do
end
end
+ describe 'POST update' do
+ context 'when the password has changed' do
+ def update_password(user, password, password_confirmation = nil)
+ params = {
+ id: user.to_param,
+ user: {
+ password: password,
+ password_confirmation: password_confirmation || password
+ }
+ }
+
+ post :update, params
+ end
+
+ context 'when the new password is valid' do
+ it 'redirects to the user' do
+ update_password(user, 'AValidPassword1')
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it 'updates the password' do
+ update_password(user, 'AValidPassword1')
+
+ expect { user.reload }.to change { user.encrypted_password }
+ end
+
+ it 'sets the new password to expire immediately' do
+ update_password(user, 'AValidPassword1')
+
+ expect { user.reload }.to change { user.password_expires_at }.to(a_value <= Time.now)
+ end
+ end
+
+ context 'when the new password is invalid' do
+ it 'shows the edit page again' do
+ update_password(user, 'invalid')
+
+ expect(response).to render_template(:edit)
+ end
+
+ it 'returns the error message' do
+ update_password(user, 'invalid')
+
+ expect(assigns[:user].errors).to contain_exactly(a_string_matching(/too short/))
+ end
+
+ it 'does not update the password' do
+ update_password(user, 'invalid')
+
+ expect { user.reload }.not_to change { user.encrypted_password }
+ end
+ end
+
+ context 'when the new password does not match the password confirmation' do
+ it 'shows the edit page again' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect(response).to render_template(:edit)
+ end
+
+ it 'returns the error message' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect(assigns[:user].errors).to contain_exactly(a_string_matching(/doesn't match/))
+ end
+
+ it 'does not update the password' do
+ update_password(user, 'AValidPassword1', 'AValidPassword2')
+
+ expect { user.reload }.not_to change { user.encrypted_password }
+ end
+ end
+ end
+ end
+
describe "POST impersonate" do
context "when the user is blocked" do
before do
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
new file mode 100644
index 00000000000..0d8a68bb51a
--- /dev/null
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -0,0 +1,105 @@
+require 'spec_helper'
+
+describe HealthCheckController do
+ let(:token) { current_application_settings.health_check_access_token }
+ let(:json_response) { JSON.parse(response.body) }
+ let(:xml_response) { Hash.from_xml(response.body)['hash'] }
+
+ describe 'GET #index' do
+ context 'when services are up but NO access token' do
+ it 'returns a not found page' do
+ get :index
+ expect(response).to be_not_found
+ end
+ end
+
+ context 'when services are up and an access token is provided' do
+ it 'supports passing the token in the header' do
+ request.headers['TOKEN'] = token
+ get :index
+ expect(response).to be_success
+ expect(response.content_type).to eq 'text/plain'
+ end
+
+ it 'supports successful plaintest response' do
+ get :index, token: token
+ expect(response).to be_success
+ expect(response.content_type).to eq 'text/plain'
+ end
+
+ it 'supports successful json response' do
+ get :index, token: token, format: :json
+ expect(response).to be_success
+ expect(response.content_type).to eq 'application/json'
+ expect(json_response['healthy']).to be true
+ end
+
+ it 'supports successful xml response' do
+ get :index, token: token, format: :xml
+ expect(response).to be_success
+ expect(response.content_type).to eq 'application/xml'
+ expect(xml_response['healthy']).to be true
+ end
+
+ it 'supports successful responses for specific checks' do
+ get :index, token: token, checks: 'email', format: :json
+ expect(response).to be_success
+ expect(response.content_type).to eq 'application/json'
+ expect(json_response['healthy']).to be true
+ end
+ end
+
+ context 'when a service is down but NO access token' do
+ it 'returns a not found page' do
+ get :index
+ expect(response).to be_not_found
+ end
+ end
+
+ context 'when a service is down and an access token is provided' do
+ before do
+ allow(HealthCheck::Utils).to receive(:process_checks).with('standard').and_return('The server is on fire')
+ allow(HealthCheck::Utils).to receive(:process_checks).with('email').and_return('Email is on fire')
+ end
+
+ it 'supports passing the token in the header' do
+ request.headers['TOKEN'] = token
+ get :index
+ expect(response.status).to eq(500)
+ expect(response.content_type).to eq 'text/plain'
+ expect(response.body).to include('The server is on fire')
+ end
+
+ it 'supports failure plaintest response' do
+ get :index, token: token
+ expect(response.status).to eq(500)
+ expect(response.content_type).to eq 'text/plain'
+ expect(response.body).to include('The server is on fire')
+ end
+
+ it 'supports failure json response' do
+ get :index, token: token, format: :json
+ expect(response.status).to eq(500)
+ expect(response.content_type).to eq 'application/json'
+ expect(json_response['healthy']).to be false
+ expect(json_response['message']).to include('The server is on fire')
+ end
+
+ it 'supports failure xml response' do
+ get :index, token: token, format: :xml
+ expect(response.status).to eq(500)
+ expect(response.content_type).to eq 'application/xml'
+ expect(xml_response['healthy']).to be false
+ expect(xml_response['message']).to include('The server is on fire')
+ end
+
+ it 'supports failure responses for specific checks' do
+ get :index, token: token, checks: 'email', format: :json
+ expect(response.status).to eq(500)
+ expect(response.content_type).to eq 'application/json'
+ expect(json_response['healthy']).to be false
+ expect(json_response['message']).to include('Email is on fire')
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 8ad73472117..c4b4a888b4e 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -122,27 +122,23 @@ describe Projects::BranchesController do
let(:branch) { "feature" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "valid branch name with unencoded slashes" do
let(:branch) { "improve/awesome" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "invalid branch name, valid ref" do
let(:branch) { "no-branch" }
it { expect(response.status).to eq(404) }
- it { expect(subject).to render_template('destroy') }
end
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 788a609ee40..4018dac95a2 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs).first).to_not be_nil
+ expect(assigns(:diffs).first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
end
@@ -32,7 +32,7 @@ describe Projects::CompareController do
w: 1)
expect(response).to be_success
- expect(assigns(:diffs).first).to_not be_nil
+ expect(assigns(:diffs).first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
# without whitespace option, there are more than 2 diff_splits
diff_splits = assigns(:diffs).first.diff.split("\n")
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 40bd83af861..fbe8758dda7 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -28,7 +28,7 @@ describe Projects::GroupLinksController do
expect(group.shared_projects).to include project
end
- it 'redirects to project group links page'do
+ it 'redirects to project group links page' do
expect(response).to redirect_to(
namespace_project_group_links_path(project.namespace, project)
)
@@ -43,7 +43,7 @@ describe Projects::GroupLinksController do
end
it 'does not share project with that group' do
- expect(group.shared_projects).to_not include project
+ expect(group.shared_projects).not_to include project
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 2b2ad3b9412..c469480b086 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -56,7 +56,7 @@ describe Projects::IssuesController do
move_issue
expect(response).to have_http_status :found
- expect(another_project.issues).to_not be_empty
+ expect(another_project.issues).not_to be_empty
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c0a1f45195f..4f621a43d7e 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -63,7 +63,7 @@ describe Projects::MergeRequestsController do
id: merge_request.iid,
format: format)
- expect(response.body).to eq((merge_request.send(:"to_#{format}")).to_s)
+ expect(response.body).to eq(merge_request.send(:"to_#{format}").to_s)
end
it "should not escape Html" do
diff --git a/spec/controllers/projects/notification_settings_controller_spec.rb b/spec/controllers/projects/notification_settings_controller_spec.rb
index 4908b545648..c5d17d97ec9 100644
--- a/spec/controllers/projects/notification_settings_controller_spec.rb
+++ b/spec/controllers/projects/notification_settings_controller_spec.rb
@@ -34,5 +34,19 @@ describe Projects::NotificationSettingsController do
expect(response.status).to eq 200
end
end
+
+ context 'not authorized' do
+ let(:private_project) { create(:project, :private) }
+ before { sign_in(user) }
+
+ it 'returns 404' do
+ put :update,
+ namespace_id: private_project.namespace.to_param,
+ project_id: private_project.to_param,
+ notification_setting: { level: :participating }
+
+ expect(response.status).to eq(404)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index ed64e7cf9af..750fbecdd07 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -38,7 +38,7 @@ describe Projects::ProjectMembersController do
include_context 'import applied'
it 'does not import team members' do
- expect(project.team_members).to_not include member
+ expect(project.team_members).not_to include member
end
it 'responds with not found' do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 1caa476d37d..fb29274c687 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -42,7 +42,7 @@ describe Projects::RawController do
before do
public_project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.render nothing: true }
+ allow(controller).to receive(:send_file) { controller.head :ok }
end
it 'serves the file' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 069cd917e5a..fba545560c7 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -8,6 +8,40 @@ describe ProjectsController do
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
describe "GET show" do
+ context "user not project member" do
+ before { sign_in(user) }
+
+ context "user does not have access to project" do
+ let(:private_project) { create(:project, :private) }
+
+ it "does not initialize notification setting" do
+ get :show, namespace_id: private_project.namespace.path, id: private_project.path
+ expect(assigns(:notification_setting)).to be_nil
+ end
+ end
+
+ context "user has access to project" do
+ context "and does not have notification setting" do
+ it "initializes notification as disabled" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(assigns(:notification_setting).level).to eq("global")
+ end
+ end
+
+ context "and has notification setting" do
+ before do
+ setting = user.notification_settings_for(public_project)
+ setting.level = :watch
+ setting.save
+ end
+
+ it "shows current notification setting" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(assigns(:notification_setting).level).to eq("watch")
+ end
+ end
+ end
+ end
context "rendering default project view" do
render_views
@@ -81,6 +115,17 @@ describe ProjectsController do
expect(public_project_with_dot_atom).not_to be_valid
end
end
+
+ context 'when the project is pending deletions' do
+ it 'renders a 404 error' do
+ project = create(:project, pending_delete: true)
+ sign_in(user)
+
+ get :show, namespace_id: project.namespace.path, id: project.path
+
+ expect(response.status).to eq 404
+ end
+ end
end
describe "#update" do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
new file mode 100644
index 00000000000..209fa37d97d
--- /dev/null
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe RegistrationsController do
+ describe '#create' do
+ around(:each) do |example|
+ perform_enqueued_jobs do
+ example.run
+ end
+ end
+
+ let(:user_params) { { user: { name: "new_user", username: "new_username", email: "new@user.com", password: "Any_password" } } }
+
+ context 'when sending email confirmation' do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) }
+
+ it 'logs user in directly' do
+ post(:create, user_params)
+ expect(ActionMailer::Base.deliveries.last).to be_nil
+ expect(subject.current_user).not_to be_nil
+ end
+ end
+
+ context 'when not sending email confirmation' do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
+
+ it 'does not authenticate user and sends confirmation email' do
+ post(:create, user_params)
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(subject.current_user).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 83cc8ec6d26..5dc8724fb50 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -12,7 +12,7 @@ describe SessionsController do
post(:create, user: { login: 'invalid', password: 'invalid' })
expect(response)
- .to set_flash.now[:alert].to /Invalid login or password/
+ .to set_flash.now[:alert].to /Invalid Login or password/
end
end
@@ -35,6 +35,27 @@ describe SessionsController do
post(:create, { user: user_params }, { otp_user_id: user.id })
end
+ context 'remember_me field' do
+ it 'sets a remember_user_token cookie when enabled' do
+ allow(controller).to receive(:find_user).and_return(user)
+ expect(controller).
+ to receive(:remember_me).with(user).and_call_original
+
+ authenticate_2fa(remember_me: '1', otp_attempt: user.current_otp)
+
+ expect(response.cookies['remember_user_token']).to be_present
+ end
+
+ it 'does nothing when disabled' do
+ allow(controller).to receive(:find_user).and_return(user)
+ expect(controller).not_to receive(:remember_me)
+
+ authenticate_2fa(remember_me: '0', otp_attempt: user.current_otp)
+
+ expect(response.cookies['remember_user_token']).to be_nil
+ end
+ end
+
##
# See #14900 issue
#
@@ -47,7 +68,7 @@ describe SessionsController do
authenticate_2fa(login: another_user.username,
otp_attempt: another_user.current_otp)
- expect(subject.current_user).to_not eq another_user
+ expect(subject.current_user).not_to eq another_user
end
end
@@ -56,7 +77,7 @@ describe SessionsController do
authenticate_2fa(login: another_user.username,
otp_attempt: 'invalid')
- expect(subject.current_user).to_not eq another_user
+ expect(subject.current_user).not_to eq another_user
end
end
@@ -73,7 +94,7 @@ describe SessionsController do
before { authenticate_2fa(otp_attempt: 'invalid') }
it 'does not authenticate' do
- expect(subject.current_user).to_not eq user
+ expect(subject.current_user).not_to eq user
end
it 'warns about invalid OTP code' do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 8045c8b940d..c61ec174665 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -112,4 +112,26 @@ describe UsersController do
expect(response).to render_template('calendar_activities')
end
end
+
+ describe 'GET #snippets' do
+ before do
+ sign_in(user)
+ end
+
+ context 'format html' do
+ it 'renders snippets page' do
+ get :snippets, username: user.username
+ expect(response.status).to eq(200)
+ expect(response).to render_template('show')
+ end
+ end
+
+ context 'format json' do
+ it 'response with snippets json data' do
+ get :snippets, username: user.username, format: :json
+ expect(response.status).to eq(200)
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
+ end
+ end
end
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index d0e8c778518..8f6422a7825 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index c80e7366551..efe9803b1a7 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string(255)
-# font :string(255)
-#
-
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index 19a54946fe0..b16c1272e68 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index 2939d4307c5..3580174e873 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string(255)
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :label_link do
label
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index ea2be8928d5..eb489099854 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# 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)
-#
-
FactoryGirl.define do
factory :label do
sequence(:title) { |n| "label#{n}" }
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 327858ce435..a81645acd2b 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects
-#
-# id :integer not null, primary key
-# oid :string(255) not null
-# size :integer not null
-# created_at :datetime
-# updated_at :datetime
-# file :string(255)
-#
-
include ActionDispatch::TestProcess
FactoryGirl.define do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 50b45843c99..1ed0355c8e4 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects_projects
-#
-# id :integer not null, primary key
-# lfs_object_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index e281e2f227b..c6a08d78b78 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,32 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# 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_params :text
-# merge_when_build_succeeds :boolean default(FALSE), not null
-# merge_user_id :integer
-# merge_commit_sha :string
-#
-
FactoryGirl.define do
factory :merge_request do
title
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index e5dcb159014..c32e205ee69 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string(255)
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-# is_award :boolean default(FALSE), not null
-#
-
require_relative '../support/repo_helpers'
include ActionDispatch::TestProcess
@@ -28,41 +7,39 @@ FactoryGirl.define do
project
note "Note"
author
+ on_issue
factory :note_on_commit, traits: [:on_commit]
- factory :note_on_commit_diff, traits: [:on_commit, :on_diff]
+ factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
- factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
+ factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
factory :downvote_note, traits: [:award, :downvote]
factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do
- project
+ noteable nil
+ noteable_id nil
+ noteable_type 'Commit'
commit_id RepoHelpers.sample_commit.id
- noteable_type "Commit"
end
trait :on_diff do
line_code "0_184_184"
end
- trait :on_merge_request do
- project
- noteable_id 1
- noteable_type "MergeRequest"
+ trait :on_issue do
+ noteable { create(:issue, project: project) }
end
- trait :on_issue do
- noteable_id 1
- noteable_type "Issue"
+ trait :on_merge_request do
+ noteable { create(:merge_request, source_project: project) }
end
trait :on_project_snippet do
- noteable_id 1
- noteable_type "Snippet"
+ noteable { create(:snippet, project: project) }
end
trait :system do
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index 7700b15d538..ccf02d0719b 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: oauth_access_tokens
-#
-# id :integer not null, primary key
-# resource_owner_id :integer
-# application_id :integer
-# token :string not null
-# refresh_token :string
-# expires_in :integer
-# revoked_at :datetime
-# created_at :datetime not null
-# scopes :string
-#
-
FactoryGirl.define do
factory :oauth_access_token do
resource_owner
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index ecd0e44dd62..da8d97c9f82 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,42 +1,3 @@
-# == Schema Information
-#
-# 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
-# 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
-#
-
FactoryGirl.define do
# Project without repository
#
@@ -60,6 +21,12 @@ FactoryGirl.define do
trait :private do
visibility_level Gitlab::VisibilityLevel::PRIVATE
end
+
+ trait :empty_repo do
+ after(:create) do |project|
+ project.create_repository
+ end
+ end
end
# Project with empty repository
@@ -67,9 +34,7 @@ FactoryGirl.define do
# This is a case when you just created a project
# but not pushed any code there yet
factory :project_empty_repo, parent: :empty_project do
- after :create do |project|
- project.create_repository
- end
+ empty_repo
end
# Project with test repository
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 7f331c37256..74497dc82c0 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string(255)
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 7ae06c27840..f426e27afed 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
FactoryGirl.define do
factory :todo do
project
@@ -36,5 +18,9 @@ FactoryGirl.define do
commit_id RepoHelpers.sample_commit.id
target_type "Commit"
end
+
+ trait :build_failed do
+ action { Todo::BUILD_FAILED }
+ end
end
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 62de081661d..675d9bd18b7 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -5,8 +5,8 @@ describe 'factories' do
describe "#{factory.name} factory" do
let(:entity) { build(factory.name) }
- it 'does not raise error when created 'do
- expect { entity }.to_not raise_error
+ it 'does not raise error when created' do
+ expect { entity }.not_to raise_error
end
it 'should be valid', if: factory.build_class < ActiveRecord::Base do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 2e9851fb442..7bbe20fec43 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -19,6 +19,7 @@ describe 'Admin Builds' do
visit admin_builds_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_selector('.row-content-block', text: 'All builds')
expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all'
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
new file mode 100644
index 00000000000..dec2dedf2b5
--- /dev/null
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+feature "Admin Health Check", feature: true do
+ include WaitForAjax
+
+ before do
+ login_as :admin
+ end
+
+ describe '#show' do
+ before do
+ visit admin_health_check_path
+ end
+
+ it { page.has_text? 'Health Check' }
+ it { page.has_text? 'Health information can be retrieved' }
+
+ it 'has a health check access token' do
+ token = current_application_settings.health_check_access_token
+ expect(page).to have_content("Access token is #{token}")
+ expect(page).to have_selector('#health-check-token', text: token)
+ end
+
+ describe 'reload access token', js: true do
+ it 'changes the access token' do
+ orig_token = current_application_settings.health_check_access_token
+ click_button 'Reset health check access token'
+ wait_for_ajax
+ expect(find('#health-check-token').text).not_to eq orig_token
+ end
+ end
+ end
+
+ context 'when services are up' do
+ before do
+ visit admin_health_check_path
+ end
+
+ it 'shows healthy status' do
+ expect(page).to have_content('Current Status: Healthy')
+ end
+ end
+
+ context 'when a service is down' do
+ before do
+ allow(HealthCheck::Utils).to receive(:process_checks).and_return('The server is on fire')
+ visit admin_health_check_path
+ end
+
+ it 'shows unhealthy status' do
+ expect(page).to have_content('Current Status: Unhealthy')
+ expect(page).to have_content('The server is on fire')
+ end
+ end
+end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 26d03944b8a..8ebd4a6808e 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -79,7 +79,7 @@ describe "Admin Runners" do
end
it 'changes registration token' do
- expect(page_token).to_not eq token
+ expect(page_token).not_to eq token
end
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 4570e409128..96621843b30 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -152,7 +152,7 @@ describe "Admin::Users", feature: true do
it 'sees impersonation log out icon' do
icon = first('.fa.fa-user-secret')
- expect(icon).to_not eql nil
+ expect(icon).not_to eql nil
end
it 'can log out of impersonated user back to original user' do
@@ -210,6 +210,8 @@ describe "Admin::Users", feature: true do
before do
fill_in "user_name", with: "Big Bang"
fill_in "user_email", with: "bigbang@mail.com"
+ fill_in "user_password", with: "AValidPassword1"
+ fill_in "user_password_confirmation", with: "AValidPassword1"
check "user_admin"
click_button "Save changes"
end
@@ -223,6 +225,7 @@ describe "Admin::Users", feature: true do
@simple_user.reload
expect(@simple_user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy
+ expect(@simple_user.password_expires_at).to be <= Time.now
end
end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 090a941958f..7a05d30e8b5 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -46,7 +46,7 @@ describe "Builds" do
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
- it { expect(page).to_not have_link 'Cancel running' }
+ it { expect(page).not_to have_link 'Cancel running' }
end
end
@@ -62,7 +62,7 @@ describe "Builds" do
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
- it { expect(page).to_not have_link 'Cancel running' }
+ it { expect(page).not_to have_link 'Cancel running' }
end
describe "GET /:project/builds/:id" do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index dacaa96d760..20f0b27bcc1 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -137,8 +137,8 @@ describe 'Commits' do
expect(page).to have_content commit.git_commit_message
expect(page).to have_content commit.git_author_name
expect(page).to have_link('Download artifacts')
- expect(page).to_not have_link('Cancel running')
- expect(page).to_not have_link('Retry failed')
+ expect(page).not_to have_link('Cancel running')
+ expect(page).not_to have_link('Retry failed')
end
end
@@ -155,9 +155,9 @@ describe 'Commits' do
expect(page).to have_content commit.sha[0..7]
expect(page).to have_content commit.git_commit_message
expect(page).to have_content commit.git_author_name
- expect(page).to_not have_link('Download artifacts')
- expect(page).to_not have_link('Cancel running')
- expect(page).to_not have_link('Retry failed')
+ expect(page).not_to have_link('Download artifacts')
+ expect(page).not_to have_link('Cancel running')
+ expect(page).not_to have_link('Retry failed')
end
end
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
new file mode 100644
index 00000000000..53b4f027117
--- /dev/null
+++ b/spec/features/container_registry_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe "Container Registry" do
+ let(:project) { create(:empty_project) }
+ let(:repository) { project.container_registry_repository }
+ let(:tag_name) { 'latest' }
+ let(:tags) { [tag_name] }
+
+ before do
+ login_as(:user)
+ project.team << [@user, :developer]
+ stub_container_registry_tags(*tags)
+ stub_container_registry_config(enabled: true)
+ allow(Auth::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token')
+ end
+
+ describe 'GET /:project/container_registry' do
+ before do
+ visit namespace_project_container_registry_index_path(project.namespace, project)
+ end
+
+ context 'when no tags' do
+ let(:tags) { [] }
+
+ it { expect(page).to have_content('No images in Container Registry for this project') }
+ end
+
+ context 'when there are tags' do
+ it { expect(page).to have_content(tag_name)}
+ end
+ end
+
+ describe 'DELETE /:project/container_registry/tag' do
+ before do
+ visit namespace_project_container_registry_index_path(project.namespace, project)
+ end
+
+ it do
+ expect_any_instance_of(::ContainerRegistry::Tag).to receive(:delete).and_return(true)
+
+ click_on 'Remove'
+ end
+ end
+end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 192e3619375..7efbaaa048c 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -154,4 +154,180 @@ describe 'Filter issues', feature: true do
end
end
end
+
+ describe 'filter issues by text' do
+ before do
+ create(:issue, title: "Bug", project: project)
+
+ bug_label = create(:label, project: project, title: 'bug')
+ milestone = create(:milestone, title: "8", project: project)
+
+ issue = create(:issue,
+ title: "Bug 2",
+ project: project,
+ milestone: milestone,
+ author: user,
+ assignee: user)
+ issue.labels << bug_label
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ context 'only text', js: true do
+ it 'should filter issues by searched text' do
+ fill_in 'issue_search', with: 'Bug'
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+ end
+
+ it 'should not show any issues' do
+ fill_in 'issue_search', with: 'testing'
+
+ page.within '.issues-list' do
+ expect(page).not_to have_selector('.issue')
+ end
+ end
+ end
+
+ context 'text and dropdown options', js: true do
+ it 'should filter by text and label' do
+ fill_in 'issue_search', with: 'Bug'
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ click_button 'Label'
+ page.within '.labels-filter' do
+ click_link 'bug'
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 1)
+ end
+ end
+
+ it 'should filter by text and milestone' do
+ fill_in 'issue_search', with: 'Bug'
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ click_button 'Milestone'
+ page.within '.milestone-filter' do
+ click_link '8'
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 1)
+ end
+ end
+
+ it 'should filter by text and assignee' do
+ fill_in 'issue_search', with: 'Bug'
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ click_button 'Assignee'
+ page.within '.dropdown-menu-assignee' do
+ click_link user.name
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 1)
+ end
+ end
+
+ it 'should filter by text and author' do
+ fill_in 'issue_search', with: 'Bug'
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ click_button 'Author'
+ page.within '.dropdown-menu-author' do
+ click_link user.name
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 1)
+ end
+ end
+ end
+ end
+
+ describe 'filter issues and sort', js: true do
+ before do
+ bug_label = create(:label, project: project, title: 'bug')
+ bug_one = create(:issue, title: "Frontend", project: project)
+ bug_two = create(:issue, title: "Bug 2", project: project)
+
+ bug_one.labels << bug_label
+ bug_two.labels << bug_label
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'should be able to filter and sort issues' do
+ click_button 'Label'
+ page.within '.labels-filter' do
+ click_link 'bug'
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: 2)
+ end
+
+ click_button 'Last created'
+ page.within '.dropdown-menu-sort' do
+ click_link 'Oldest created'
+ end
+
+ page.within '.issues-list' do
+ expect(first('.issue')).to have_content('Frontend')
+ end
+ end
+ end
+
+ describe 'filter by any author', js: true do
+ before do
+ user2 = create(:user, name: "tester")
+ create(:issue, project: project, author: user)
+ create(:issue, project: project, author: user2)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'should show filter by any author link' do
+ click_button "Author"
+ fill_in "Search authors", with: "tester"
+
+ page.within ".dropdown-menu-author" do
+ expect(page).to have_content "tester"
+ end
+ end
+
+ it 'should show filter issues by any author' do
+ page.within '.issues-list' do
+ expect(page).to have_selector ".issue", count: 2
+ end
+
+ click_button "Author"
+ fill_in "Search authors", with: "tester"
+
+ page.within ".dropdown-menu-author" do
+ click_link "tester"
+ end
+
+ page.within '.issues-list' do
+ expect(page).to have_selector ".issue", count: 1
+ end
+ end
+ end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 84c8e20ebaa..c7019c5aea1 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -19,7 +19,7 @@ feature 'issue move to another project' do
end
scenario 'moving issue to another project not allowed' do
- expect(page).to have_no_select('move_to_project_id')
+ expect(page).to have_no_selector('#move_to_project_id')
end
end
@@ -37,7 +37,7 @@ feature 'issue move to another project' do
end
scenario 'moving issue to another project' do
- select(new_project.name_with_namespace, from: 'move_to_project_id')
+ first('#move_to_project_id', visible: false).set(new_project.id)
click_button('Save changes')
expect(current_url).to include project_path(new_project)
@@ -47,14 +47,18 @@ feature 'issue move to another project' do
expect(page).to have_content(issue.title)
end
- context 'projects user does not have permission to move issue to exist' do
+ context 'user does not have permission to move the issue to a project', js: true do
let!(:private_project) { create(:project, :private) }
let(:another_project) { create(:project) }
background { another_project.team << [user, :guest] }
scenario 'browsing projects in projects select' do
- options = [ '', 'No project', new_project.name_with_namespace ]
- expect(page).to have_select('move_to_project_id', options: options)
+ click_link 'Select project'
+
+ page.within '.select2-results' do
+ expect(page).to have_content 'No project'
+ expect(page).to have_content new_project.name_with_namespace
+ end
end
end
@@ -65,7 +69,7 @@ feature 'issue move to another project' do
end
scenario 'user wants to move issue that has already been moved' do
- expect(page).to have_no_select('move_to_project_id')
+ expect(page).to have_no_selector('#move_to_project_id')
end
end
end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index e4efdbe2421..f5cfe2d666e 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -9,8 +9,11 @@ feature 'Issue notes polling' do
end
scenario 'Another user adds a comment to an issue', js: true do
- note = create(:note_on_issue, noteable: issue, note: 'Looks good!')
+ note = create(:note, noteable: issue, project: project,
+ note: 'Looks good!')
+
page.execute_script('notes.refresh();')
+
expect(page).to have_selector("#note_#{note.id}", text: 'Looks good!')
end
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index b03dd0f666d..466a6f7dfa7 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -95,7 +95,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
find('.dropdown-menu-milestone a', text: "No Milestone").click
click_update_issues_button
- expect(first('.issue')).to_not have_content milestone.title
+ expect(first('.issue')).not_to have_content milestone.title
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d5755c293c5..9271964166a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -64,10 +64,68 @@ describe 'Issues', feature: true do
end
end
+ describe 'due date', js: true do
+ context 'on new form' do
+ before do
+ visit new_namespace_project_issue_path(project.namespace, project)
+ end
+
+ it 'should save with due date' do
+ date = Date.today.at_beginning_of_month
+
+ fill_in 'issue_title', with: 'bug 345'
+ fill_in 'issue_description', with: 'bug description'
+
+ page.within '.datepicker' do
+ click_link date.day
+ end
+
+ expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+
+ click_button 'Submit issue'
+
+ page.within '.issuable-sidebar' do
+ expect(page).to have_content date.to_s(:medium)
+ end
+ end
+ end
+
+ context 'on edit form' do
+ let(:issue) { create(:issue, author: @user,project: project, due_date: Date.today.at_beginning_of_month.to_s) }
+
+ before do
+ visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'should save with due date' do
+ date = Date.today.at_beginning_of_month
+
+ expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+
+ date = date.tomorrow
+
+ fill_in 'issue_title', with: 'bug 345'
+ fill_in 'issue_description', with: 'bug description'
+
+ page.within '.datepicker' do
+ click_link date.day
+ end
+
+ expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ expect(page).to have_content date.to_s(:medium)
+ end
+ end
+ end
+ end
+
describe 'Issue info' do
it 'excludes award_emoji from comment count' do
issue = create(:issue, author: @user, assignee: @user, project: project, title: 'foobar')
- create(:upvote_note, noteable: issue)
+ create(:upvote_note, noteable: issue, project: project)
visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
@@ -331,7 +389,7 @@ describe 'Issues', feature: true do
page.within '.assignee' do
click_link 'Edit'
end
-
+
page.within '.dropdown-menu-user' do
click_link @user.name
end
@@ -431,6 +489,43 @@ describe 'Issues', feature: true do
end
end
+ describe 'due date' do
+ context 'update due on issue#show', js: true do
+ let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
+
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'should add due date to issue' do
+ page.within '.due_date' do
+ click_link 'Edit'
+
+ page.within '.ui-datepicker-calendar' do
+ first('.ui-state-default').click
+ end
+
+ expect(page).to have_no_content 'None'
+ end
+ end
+
+ it 'should remove due date from issue' do
+ page.within '.due_date' do
+ click_link 'Edit'
+
+ page.within '.ui-datepicker-calendar' do
+ first('.ui-state-default').click
+ end
+
+ expect(page).to have_no_content 'None'
+
+ click_link 'remove due date'
+ expect(page).to have_content 'None'
+ end
+ end
+ end
+ end
+
def first_issue
page.all('ul.issues-list > li').first.text
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 8c38dd5b122..c1b178c3b6c 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -32,7 +32,7 @@ feature 'Login', feature: true do
let(:user) { create(:user, :two_factor) }
before do
- login_with(user)
+ login_with(user, remember: true)
expect(page).to have_content('Two-factor Authentication')
end
@@ -52,6 +52,12 @@ feature 'Login', feature: true do
expect(current_path).to eq root_path
end
+ it 'persists remember_me value via hidden field' do
+ field = first('input#user_remember_me', visible: false)
+
+ expect(field.value).to eq '1'
+ end
+
it 'blocks login with invalid code' do
enter_code('foo')
expect(page).to have_content('Invalid two-factor code')
@@ -121,7 +127,7 @@ feature 'Login', feature: true do
user = create(:user, password: 'not-the-default')
login_with(user)
- expect(page).to have_content('Invalid login or password.')
+ expect(page).to have_content('Invalid Login or password.')
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 0148c87084a..1d892fe1a55 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -278,6 +278,10 @@ describe 'GitLab Markdown', feature: true do
it 'includes GollumTagsFilter' do
expect(doc).to parse_gollum_tags
end
+
+ it 'includes InlineDiffFilter' do
+ expect(doc).to parse_inline_diffs
+ end
end
# Fake a `current_user` helper
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
new file mode 100644
index 00000000000..edc0bdec3db
--- /dev/null
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+feature 'Merge request created from fork' do
+ given(:user) { create(:user) }
+ given(:project) { create(:project, :public) }
+ given(:fork_project) { create(:project, :public) }
+
+ given!(:merge_request) do
+ create(:forked_project_link, forked_to_project: fork_project,
+ forked_from_project: project)
+
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: project,
+ description: 'Test merge request')
+ end
+
+ background do
+ fork_project.team << [user, :master]
+ login_as user
+ end
+
+ scenario 'user can access merge request' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_content 'Test merge request'
+ end
+
+ context 'pipeline present in source project' do
+ include WaitForAjax
+
+ given(:pipeline) do
+ create(:ci_commit_with_two_jobs, project: fork_project,
+ sha: merge_request.last_commit.id,
+ ref: merge_request.source_branch)
+ end
+
+ background { pipeline.create_builds(user) }
+
+ scenario 'user visits a pipelines page', js: true do
+ visit_merge_request(merge_request)
+ page.within('.merge-request-tabs') { click_link 'Builds' }
+ wait_for_ajax
+
+ page.within('table.builds') do
+ expect(page).to have_content 'rspec'
+ expect(page).to have_content 'spinach'
+ end
+
+ expect(find_link('Cancel running')[:href])
+ .to include fork_project.path_with_namespace
+ end
+ end
+
+ def visit_merge_request(mr)
+ visit namespace_project_merge_request_path(project.namespace,
+ project, mr)
+ end
+end
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
new file mode 100644
index 00000000000..1c130057c56
--- /dev/null
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -0,0 +1,161 @@
+require 'spec_helper'
+
+describe 'Projects > Merge requests > User lists merge requests', feature: true do
+ include SortingHelper
+
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+
+ before do
+ @fix = create(:merge_request,
+ title: 'fix',
+ source_project: project,
+ source_branch: 'fix',
+ assignee: user,
+ milestone: create(:milestone, due_date: '2013-12-11'),
+ created_at: 1.minute.ago,
+ updated_at: 1.minute.ago)
+ create(:merge_request,
+ title: 'markdown',
+ source_project: project,
+ source_branch: 'markdown',
+ assignee: user,
+ milestone: create(:milestone, due_date: '2013-12-12'),
+ created_at: 2.minutes.ago,
+ updated_at: 2.minutes.ago)
+ create(:merge_request,
+ title: 'lfs',
+ source_project: project,
+ source_branch: 'lfs',
+ created_at: 3.minutes.ago,
+ updated_at: 10.seconds.ago)
+ end
+
+ it 'filters on no assignee' do
+ visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
+
+ expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
+ expect(page).to have_content 'lfs'
+ expect(page).not_to have_content 'fix'
+ expect(page).not_to have_content 'markdown'
+ expect(count_merge_requests).to eq(1)
+ end
+
+ it 'filters on a specific assignee' do
+ visit_merge_requests(project, assignee_id: user.id)
+
+ expect(page).not_to have_content 'lfs'
+ expect(page).to have_content 'fix'
+ expect(page).to have_content 'markdown'
+ expect(count_merge_requests).to eq(2)
+ end
+
+ it 'sorts by newest' do
+ visit_merge_requests(project, sort: sort_value_recently_created)
+
+ expect(first_merge_request).to include('lfs')
+ expect(last_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'sorts by oldest' do
+ visit_merge_requests(project, sort: sort_value_oldest_created)
+
+ expect(first_merge_request).to include('fix')
+ expect(last_merge_request).to include('lfs')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'sorts by last updated' do
+ visit_merge_requests(project, sort: sort_value_recently_updated)
+
+ expect(first_merge_request).to include('lfs')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'sorts by oldest updated' do
+ visit_merge_requests(project, sort: sort_value_oldest_updated)
+
+ expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'sorts by milestone due soon' do
+ visit_merge_requests(project, sort: sort_value_milestone_soon)
+
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'sorts by milestone due later' do
+ visit_merge_requests(project, sort: sort_value_milestone_later)
+
+ expect(first_merge_request).to include('markdown')
+ expect(count_merge_requests).to eq(3)
+ end
+
+ it 'filters on one label and sorts by due soon' do
+ label = create(:label, project: project)
+ create(:label_link, label: label, target: @fix)
+
+ visit_merge_requests(project, label_name: [label.name],
+ sort: sort_value_due_date_soon)
+
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
+ end
+
+ context 'while filtering on two labels' do
+ let(:label) { create(:label, project: project) }
+ let(:label2) { create(:label, project: project) }
+
+ before do
+ create(:label_link, label: label, target: @fix)
+ create(:label_link, label: label2, target: @fix)
+ end
+
+ it 'sorts by due soon' do
+ visit_merge_requests(project, label_name: [label.name, label2.name],
+ sort: sort_value_due_date_soon)
+
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
+ end
+
+ context 'filter on assignee and' do
+ it 'sorts by due soon' do
+ visit_merge_requests(project, label_name: [label.name, label2.name],
+ assignee_id: user.id,
+ sort: sort_value_due_date_soon)
+
+ expect(first_merge_request).to include('fix')
+ expect(count_merge_requests).to eq(1)
+ end
+
+ it 'sorts by recently due milestone' do
+ visit namespace_project_merge_requests_path(project.namespace, project,
+ label_name: [label.name, label2.name],
+ assignee_id: user.id,
+ sort: sort_value_milestone_soon)
+
+ expect(first_merge_request).to include('fix')
+ end
+ end
+ end
+
+ def visit_merge_requests(project, opts = {})
+ visit namespace_project_merge_requests_path(project.namespace, project, opts)
+ end
+
+ def first_merge_request
+ page.all('ul.mr-list > li').first.text
+ end
+
+ def last_merge_request
+ page.all('ul.mr-list > li').last.text
+ end
+
+ def count_merge_requests
+ page.all('ul.mr-list > li').count
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 389812ff7e1..2835cf44494 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -19,10 +19,14 @@ describe 'Comments', feature: true do
end
describe 'On a merge request', js: true, feature: true do
- let!(:merge_request) { create(:merge_request) }
- let!(:project) { merge_request.source_project }
+ let!(:project) { create(:project) }
+ let!(:merge_request) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+
let!(:note) do
- create(:note_on_merge_request, :with_attachment, project: project)
+ create(:note_on_merge_request, :with_attachment, noteable: merge_request,
+ project: project)
end
before do
@@ -192,7 +196,7 @@ describe 'Comments', feature: true do
end
it 'should be removed when canceled' do
- page.within(".diff-file form[id$='#{line_code}']") do
+ page.within(".diff-file form[id$='#{line_code}-true']") do
find('.js-close-discussion-note-form').trigger('click')
end
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 1adab7e9c6c..c7c00a3266a 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -32,7 +32,8 @@ feature 'Member autocomplete', feature: true do
context 'adding a new note on a Issue', js: true do
before do
issue = create(:issue, author: author, project: project)
- create(:note, note: 'Ultralight Beam', noteable: issue, author: participant)
+ create(:note, note: 'Ultralight Beam', noteable: issue,
+ project: project, author: participant)
visit_issue(project, issue)
end
@@ -47,7 +48,8 @@ feature 'Member autocomplete', feature: true do
context 'adding a new note on a Merge Request ', js: true do
before do
merge = create(:merge_request, source_project: project, target_project: project, author: author)
- create(:note, note: 'Ultralight Beam', noteable: merge, author: participant)
+ create(:note, note: 'Ultralight Beam', noteable: merge,
+ project: project, author: participant)
visit_merge_request(project, merge)
end
diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb
new file mode 100644
index 00000000000..acd6fb3538c
--- /dev/null
+++ b/spec/features/pipelines_spec.rb
@@ -0,0 +1,189 @@
+require 'spec_helper'
+
+describe "Pipelines" do
+ include GitlabRoutingHelper
+
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ project.team << [user, :developer]
+ end
+
+ describe 'GET /:project/pipelines' do
+ let!(:pipeline) { create(:ci_commit, project: project, ref: 'master', status: 'running') }
+
+ [:all, :running, :branches].each do |scope|
+ context "displaying #{scope}" do
+ let(:project) { create(:project) }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) }
+
+ it { expect(page).to have_content(pipeline.short_sha) }
+ end
+ end
+
+ context 'anonymous access' do
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_http_status(:success) }
+ end
+
+ context 'cancelable pipeline' do
+ let!(:running) { create(:ci_build, :running, commit: pipeline, stage: 'test', commands: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_link('Cancel') }
+ it { expect(page).to have_selector('.ci-running') }
+
+ context 'when canceling' do
+ before { click_link('Cancel') }
+
+ it { expect(page).not_to have_link('Cancel') }
+ it { expect(page).to have_selector('.ci-canceled') }
+ end
+ end
+
+ context 'retryable pipelines' do
+ let!(:failed) { create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_link('Retry') }
+ it { expect(page).to have_selector('.ci-failed') }
+
+ context 'when retrying' do
+ before { click_link('Retry') }
+
+ it { expect(page).not_to have_link('Retry') }
+ it { expect(page).to have_selector('.ci-pending') }
+ end
+ end
+
+ context 'for generic statuses' do
+ context 'when running' do
+ let!(:running) { create(:generic_commit_status, status: 'running', commit: pipeline, stage: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it 'not be cancelable' do
+ expect(page).not_to have_link('Cancel')
+ end
+
+ it 'pipeline is running' do
+ expect(page).to have_selector('.ci-running')
+ end
+ end
+
+ context 'when failed' do
+ let!(:running) { create(:generic_commit_status, status: 'failed', commit: pipeline, stage: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it 'not be retryable' do
+ expect(page).not_to have_link('Retry')
+ end
+
+ it 'pipeline is failed' do
+ expect(page).to have_selector('.ci-failed')
+ end
+ end
+ end
+
+ context 'downloadable pipelines' do
+ context 'with artifacts' do
+ let!(:with_artifacts) { create(:ci_build, :artifacts, :success, commit: pipeline, name: 'rspec tests', stage: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_selector('.build-artifacts') }
+ it { expect(page).to have_link(with_artifacts.name) }
+ end
+
+ context 'without artifacts' do
+ let!(:without_artifacts) { create(:ci_build, :success, commit: pipeline, name: 'rspec', stage: 'test') }
+
+ it { expect(page).not_to have_selector('.build-artifacts') }
+ end
+ end
+ end
+
+ describe 'GET /:project/pipelines/:id' do
+ let(:pipeline) { create(:ci_commit, project: project, ref: 'master') }
+
+ before do
+ @success = create(:ci_build, :success, commit: pipeline, stage: 'build', name: 'build')
+ @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', name: 'test', commands: 'test')
+ @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy', name: 'deploy')
+ @external = create(:generic_commit_status, status: 'success', commit: pipeline, name: 'jenkins', stage: 'external')
+ end
+
+ before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ it 'showing a list of builds' do
+ expect(page).to have_content('Tests')
+ expect(page).to have_content(@success.id)
+ expect(page).to have_content('Deploy')
+ expect(page).to have_content(@failed.id)
+ expect(page).to have_content(@running.id)
+ expect(page).to have_content(@external.id)
+ expect(page).to have_content('Retry failed')
+ expect(page).to have_content('Cancel running')
+ end
+
+ context 'retrying builds' do
+ it { expect(page).not_to have_content('retried') }
+
+ context 'when retrying' do
+ before { click_on 'Retry failed' }
+
+ it { expect(page).not_to have_content('Retry failed') }
+ it { expect(page).to have_content('retried') }
+ end
+ end
+
+ context 'canceling builds' do
+ it { expect(page).not_to have_selector('.ci-canceled') }
+
+ context 'when canceling' do
+ before { click_on 'Cancel running' }
+
+ it { expect(page).not_to have_content('Cancel running') }
+ it { expect(page).to have_selector('.ci-canceled') }
+ end
+ end
+ end
+
+ describe 'POST /:project/pipelines' do
+ let(:project) { create(:project) }
+
+ before { visit new_namespace_project_pipeline_path(project.namespace, project) }
+
+ context 'for valid commit' do
+ before { fill_in('Create for', with: 'master') }
+
+ context 'with gitlab-ci.yml' do
+ before { stub_ci_commit_to_return_yaml_file }
+
+ it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) }
+ end
+
+ context 'without gitlab-ci.yml' do
+ before { click_on 'Create pipeline' }
+
+ it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
+ end
+ end
+
+ context 'for invalid commit' do
+ before do
+ fill_in('Create for', with: 'invalid reference')
+ click_on 'Create pipeline'
+ end
+
+ it { expect(page).to have_content('Reference not found') }
+ end
+ end
+end
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 13c9b95b316..51be81d634c 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -8,12 +8,10 @@ feature 'list of badges' do
project = create(:project)
project.team << [user, :master]
login_as(user)
- visit edit_namespace_project_path(project.namespace, project)
+ visit namespace_project_badges_path(project.namespace, project)
end
scenario 'user displays list of badges' do
- click_link 'Badges'
-
expect(page).to have_content 'build status'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
@@ -26,7 +24,6 @@ feature 'list of badges' do
end
scenario 'user changes current ref on badges list page', js: true do
- click_link 'Badges'
select2('improve/awesome', from: '#ref')
expect(page).to have_content 'badges/improve/awesome/build.svg'
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
new file mode 100644
index 00000000000..0c51fe72ca4
--- /dev/null
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -0,0 +1,63 @@
+require 'rails_helper'
+
+feature 'Developer views empty project instructions', feature: true do
+ let(:project) { create(:empty_project, :empty_repo) }
+ let(:developer) { create(:user) }
+
+ background do
+ project.team << [developer, :developer]
+
+ login_as(developer)
+ end
+
+ context 'without an SSH key' do
+ scenario 'defaults to HTTP' do
+ visit_project
+
+ expect_instructions_for('http')
+ end
+
+ scenario 'switches to SSH', js: true do
+ visit_project
+
+ select_protocol('SSH')
+
+ expect_instructions_for('ssh')
+ end
+ end
+
+ context 'with an SSH key' do
+ background do
+ create(:personal_key, user: developer)
+ end
+
+ scenario 'defaults to SSH' do
+ visit_project
+
+ expect_instructions_for('ssh')
+ end
+
+ scenario 'switches to HTTP', js: true do
+ visit_project
+
+ select_protocol('HTTP')
+
+ expect_instructions_for('http')
+ end
+ end
+
+ def visit_project
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ def select_protocol(protocol)
+ find('#clone-dropdown').click
+ find(".#{protocol.downcase}-selector").click
+ end
+
+ def expect_instructions_for(protocol)
+ msg = :"#{protocol.downcase}_url_to_repo"
+
+ expect(page).to have_content("git clone #{project.send(msg)}")
+ end
+end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
new file mode 100644
index 00000000000..073a83b6896
--- /dev/null
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+feature 'User wants to add a .gitignore file', feature: true do
+ include WaitForAjax
+
+ before do
+ user = create(:user)
+ project = create(:project)
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ end
+
+ scenario 'user can see .gitignore dropdown' do
+ expect(page).to have_css('.gitignore-selector')
+ end
+
+ scenario 'user can pick a .gitignore file from the dropdown', js: true do
+ find('.js-gitignore-selector').click
+ wait_for_ajax
+ within '.gitignore-selector' do
+ find('.dropdown-input-field').set('rails')
+ find('.dropdown-content li', text: 'Rails').click
+ end
+ wait_for_ajax
+
+ expect(page).to have_content('/.bundle')
+ expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
+ end
+end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 3d6ffbc4c6b..ecc818eb1e1 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -25,7 +25,7 @@ feature 'project owner creates a license file', feature: true, js: true do
file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)')
- expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
click_button 'Commit Changes'
@@ -33,7 +33,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
expect(page).to have_content('The MIT License (MIT)')
- expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
scenario 'project master creates a license file from the "Add license" link' do
@@ -48,7 +48,7 @@ feature 'project owner creates a license file', feature: true, js: true do
file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)')
- expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
click_button 'Commit Changes'
@@ -56,6 +56,6 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
expect(page).to have_content('The MIT License (MIT)')
- expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 3268e240200..34eda29c285 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -24,7 +24,7 @@ feature 'project owner sees a link to create a license file in empty project', f
file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)')
- expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
# Remove pre-receive hook so we can push without auth
@@ -34,6 +34,6 @@ feature 'project owner sees a link to create a license file in empty project', f
expect(current_path).to eq(
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
expect(page).to have_content('The MIT License (MIT)')
- expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
end
diff --git a/spec/features/project/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index 2595c4181e5..54aa9c66a08 100644
--- a/spec/features/project/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
feature 'Project shortcuts', feature: true do
- let(:project) { create(:project) }
+ let(:project) { create(:project, name: 'Victorialand') }
let(:user) { create(:user) }
describe 'On a project', js: true do
@@ -14,7 +14,7 @@ feature 'Project shortcuts', feature: true do
describe 'pressing "i"' do
it 'redirects to new issue page' do
find('body').native.send_key('i')
- expect(page).to have_content('New Issue')
+ expect(page).to have_content('Victorialand')
end
end
end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 8edeb8d18af..a5ed3595b0a 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -29,8 +29,8 @@ describe "Runners" do
end
before do
- expect(page).to_not have_content(@specific_runner3.display_name)
- expect(page).to_not have_content(@specific_runner3.display_name)
+ expect(page).not_to have_content(@specific_runner3.display_name)
+ expect(page).not_to have_content(@specific_runner3.display_name)
end
it "places runners in right places" do
@@ -110,4 +110,37 @@ describe "Runners" do
expect(page).to have_content(@specific_runner.platform)
end
end
+
+ feature 'configuring runners ability to picking untagged jobs' do
+ given(:project) { create(:empty_project) }
+ given(:runner) { create(:ci_runner) }
+
+ background do
+ project.team << [user, :master]
+ project.runners << runner
+ end
+
+ scenario 'user checks default configuration' do
+ visit namespace_project_runner_path(project.namespace, project, runner)
+
+ expect(page).to have_content 'Can run untagged jobs Yes'
+ end
+
+ context 'when runner has tags' do
+ before { runner.update_attribute(:tag_list, ['tag']) }
+
+ scenario 'user wants to prevent runner from running untagged job' do
+ visit runners_path(project)
+ page.within('.activated-specific-runners') do
+ first('small > a').click
+ end
+
+ uncheck 'runner_run_untagged'
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Can run untagged jobs No'
+ expect(runner.reload.run_untagged?).to eq false
+ end
+ end
+ end
end
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 58aabd913eb..4229e82b443 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -2,20 +2,45 @@ require 'spec_helper'
feature 'Signup', feature: true do
describe 'signup with no errors' do
- it 'creates the user account and sends a confirmation email' do
- user = build(:user)
- visit root_path
+ context "when sending confirmation email" do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
- 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"
+ it 'creates the user account and sends a confirmation email' do
+ user = build(:user)
+
+ visit root_path
+
+ 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
- expect(page).to have_content("Please check your email to confirm your account")
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content("Please check your email to confirm your account")
+ end
end
+
+ context "when not sending confirmation email" do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) }
+
+ it 'creates the user account and goes to dashboard' do
+ user = build(:user)
+
+ visit root_path
+
+ 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 dashboard_projects_path
+ expect(page).to have_content("Welcome! You have signed up successfully.")
+ end
+ end
+
end
describe 'signup with errors' do
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
new file mode 100644
index 00000000000..08a97085a9c
--- /dev/null
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+feature 'Master creates tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'with an invalid name displays an error' do
+ create_tag_in_form(tag: 'v 1.0', ref: 'master')
+
+ expect(page).to have_content 'Tag name invalid'
+ end
+
+ scenario 'with an invalid reference displays an error' do
+ create_tag_in_form(tag: 'v2.0', ref: 'foo')
+
+ expect(page).to have_content 'Target foo is invalid'
+ end
+
+ scenario 'that already exists displays an error' do
+ create_tag_in_form(tag: 'v1.1.0', ref: 'master')
+
+ expect(page).to have_content 'Tag v1.1.0 already exists'
+ end
+
+ scenario 'with multiline message displays the message in a <pre> block' do
+ create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v3.0'))
+ expect(page).to have_content 'v3.0'
+ page.within 'pre.body' do
+ expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
+ end
+ end
+
+ scenario 'with multiline release notes parses the release note as Markdown' do
+ create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v4.0'))
+ expect(page).to have_content 'v4.0'
+ page.within '.description' do
+ expect(page).to have_content 'Awesome release notes'
+ expect(page).to have_selector('ul li', count: 2)
+ end
+ end
+
+ def create_tag_in_form(tag:, ref:, message: nil, desc: nil)
+ click_link 'New tag'
+ fill_in 'tag_name', with: tag
+ fill_in 'ref', with: ref
+ fill_in 'message', with: message unless message.nil?
+ fill_in 'release_description', with: desc unless desc.nil?
+ click_button 'Create tag'
+ end
+end
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
new file mode 100644
index 00000000000..f0990118e3c
--- /dev/null
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+feature 'Master deletes tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ context 'from the tags list page' do
+ scenario 'deletes the tag' do
+ expect(page).to have_content 'v1.1.0'
+
+ page.within('.content') do
+ first('.btn-remove').click
+ end
+
+ expect(current_path).to eq(
+ namespace_project_tags_path(project.namespace, project))
+ expect(page).not_to have_content 'v1.1.0'
+ end
+
+ end
+
+ context 'from a specific tag page' do
+ scenario 'deletes the tag' do
+ click_on 'v1.0.0'
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Delete tag'
+
+ expect(current_path).to eq(
+ namespace_project_tags_path(project.namespace, project))
+ expect(page).not_to have_content 'v1.0.0'
+ end
+ end
+end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
new file mode 100644
index 00000000000..6b5b3122f72
--- /dev/null
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+feature 'Master updates tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ context 'from the tags list page' do
+ scenario 'updates the release notes' do
+ page.within(first('.content-list .controls')) do
+ click_link 'Edit release notes'
+ end
+
+ fill_in 'release_description', with: 'Awesome release notes'
+ click_button 'Save changes'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ expect(page).to have_content 'v1.1.0'
+ expect(page).to have_content 'Awesome release notes'
+ end
+ end
+
+ context 'from a specific tag page' do
+ scenario 'updates the release notes' do
+ click_on 'v1.1.0'
+ click_link 'Edit release notes'
+ fill_in 'release_description', with: 'Awesome release notes'
+ click_button 'Save changes'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ expect(page).to have_content 'v1.1.0'
+ expect(page).to have_content 'Awesome release notes'
+ end
+ end
+end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
new file mode 100644
index 00000000000..29d2c244720
--- /dev/null
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+feature 'Master views tags', feature: true do
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ end
+
+ context 'when project has no tags' do
+ let(:project) { create(:project_empty_repo) }
+ before do
+ visit namespace_project_path(project.namespace, project)
+ click_on 'README'
+ fill_in :commit_message, with: 'Add a README file', visible: true
+ # Remove pre-receive hook so we can push without auth
+ FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
+ click_button 'Commit Changes'
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'displays a specific message' do
+ expect(page).to have_content 'Repository has no tags yet.'
+ end
+ end
+
+ context 'when project has tags' do
+ let(:project) { create(:project, namespace: user.namespace) }
+ before do
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'views the tags list page' do
+ expect(page).to have_content 'v1.0.0'
+ end
+
+ scenario 'views a specific tag page' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ expect(page).to have_content 'v1.0.0'
+ expect(page).to have_content 'This tag has no release notes.'
+ end
+
+ describe 'links on the tag page' do
+ scenario 'has a button to browse files' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Browse files'
+
+ expect(current_path).to eq(
+ namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+ end
+
+ scenario 'has a button to browse commits' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Browse commits'
+
+ expect(current_path).to eq(
+ namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+ end
+ end
+ end
+end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index b7368cca29d..6ed279ef9be 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -75,7 +75,10 @@ feature 'Task Lists', feature: true do
describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) }
- let!(:note) { create(:note, note: markdown, noteable: issue, author: user) }
+ let!(:note) do
+ create(:note, note: markdown, noteable: issue,
+ project: project, author: user)
+ end
it 'renders for note body' do
visit_issue(project, issue)
diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/todos/target_state_spec.rb
new file mode 100644
index 00000000000..72491ac7e61
--- /dev/null
+++ b/spec/features/todos/target_state_spec.rb
@@ -0,0 +1,65 @@
+require 'rails_helper'
+
+feature 'Todo target states', feature: true do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ login_as user
+ end
+
+ scenario 'on a closed issue todo has closed label' do
+ issue_closed = create(:issue, state: 'closed')
+ create_todo issue_closed
+ visit dashboard_todos_path
+
+ page.within '.todos-list' do
+ expect(page).to have_content('Closed')
+ end
+ end
+
+ scenario 'on an open issue todo does not have an open label' do
+ issue_open = create(:issue)
+ create_todo issue_open
+ visit dashboard_todos_path
+
+ page.within '.todos-list' do
+ expect(page).not_to have_content('Open')
+ end
+ end
+
+ scenario 'on a merged merge request todo has merged label' do
+ mr_merged = create(:merge_request, :simple, author: user, state: 'merged')
+ create_todo mr_merged
+ visit dashboard_todos_path
+
+ page.within '.todos-list' do
+ expect(page).to have_content('Merged')
+ end
+ end
+
+ scenario 'on a closed merge request todo has closed label' do
+ mr_closed = create(:merge_request, :simple, author: user, state: 'closed')
+ create_todo mr_closed
+ visit dashboard_todos_path
+
+ page.within '.todos-list' do
+ expect(page).to have_content('Closed')
+ end
+ end
+
+ scenario 'on an open merge request todo does not have an open label' do
+ mr_open = create(:merge_request, :simple, author: user)
+ create_todo mr_open
+ visit dashboard_todos_path
+
+ page.within '.todos-list' do
+ expect(page).not_to have_content('Open')
+ end
+ end
+
+ def create_todo(target)
+ create(:todo, :mentioned, user: user, project: project, target: target, author: author)
+ end
+end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 3354f529295..4e627753cc7 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -43,6 +43,27 @@ describe 'Dashboard Todos', feature: true do
end
end
+ context 'User has Todos with labels spanning multiple projects' do
+ before do
+ label1 = create(:label, project: project)
+ note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
+ create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
+
+ project2 = create(:project)
+ label2 = create(:label, project: project2)
+ issue2 = create(:issue, project: project2)
+ note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
+ create(:todo, :mentioned, project: project2, target: issue2, user: user, note_id: note2.id)
+
+ login_as(user)
+ visit dashboard_todos_path
+ end
+
+ it 'shows page with two Todos' do
+ expect(page).to have_selector('.todos-list .todo', count: 2)
+ end
+ end
+
context 'User has multiple pages of Todos' do
before do
allow(Todo).to receive(:default_per_page).and_return(1)
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index afea1840cd7..a2b8f7b6931 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -1,24 +1,53 @@
require 'spec_helper'
-describe "Variables" do
- let(:user) { create(:user) }
- before { login_as(user) }
-
- describe "specific runners" do
- before do
- @project = FactoryGirl.create :empty_project
- @project.team << [user, :master]
+describe 'Project variables', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:variable) { create(:ci_variable, key: 'test') }
+
+ before do
+ login_as(user)
+ project.team << [user, :master]
+ project.variables << variable
+
+ visit namespace_project_variables_path(project.namespace, project)
+ end
+
+ it 'should show list of variables' do
+ page.within('.variables-table') do
+ expect(page).to have_content(variable.key)
+ end
+ end
+
+ it 'should add new variable' do
+ fill_in('variable_key', with: 'key')
+ fill_in('variable_value', with: 'key value')
+ click_button('Add new variable')
+
+ page.within('.variables-table') do
+ expect(page).to have_content('key')
+ end
+ end
+
+ it 'should delete variable' do
+ page.within('.variables-table') do
+ find('.btn-variable-delete').click
+ end
+
+ expect(page).not_to have_selector('variables-table')
+ end
+
+ it 'should edit variable' do
+ page.within('.variables-table') do
+ find('.btn-variable-edit').click
end
- it "creates variable", js: true do
- visit namespace_project_variables_path(@project.namespace, @project)
- click_on "Add a variable"
- fill_in "Key", with: "SECRET_KEY"
- fill_in "Value", with: "SECRET_VALUE"
- click_on "Save changes"
+ fill_in('variable_key', with: 'key')
+ fill_in('variable_value', with: 'key value')
+ click_button('Save variable')
- expect(page).to have_content("Variables were successfully updated.")
- expect(@project.variables.count).to eq(1)
+ page.within('.variables-table') do
+ expect(page).to have_content('key')
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index bc607a29751..ec8809e6926 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
describe IssuesFinder do
- let(:user) { create :user }
- let(:user2) { create :user }
- let(:project1) { create(:project) }
- let(:project2) { create(:project) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:project1) { create(:empty_project) }
+ let(:project2) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project1) }
let(:label) { create(:label, project: project2) }
let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
@@ -16,101 +16,147 @@ describe IssuesFinder do
project1.team << [user, :master]
project2.team << [user, :developer]
project2.team << [user2, :developer]
+
+ issue1
+ issue2
+ issue3
end
- describe :execute do
- before :each do
- issue1
- issue2
- issue3
- end
+ describe '#execute' do
+ let(:search_user) { user }
+ let(:params) { {} }
+ let(:issues) { IssuesFinder.new(search_user, params.merge(scope: scope, state: 'opened')).execute }
context 'scope: all' do
- it 'should filter by all' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(3)
+ let(:scope) { 'all' }
+
+ it 'returns all issues' do
+ expect(issues).to contain_exactly(issue1, issue2, issue3)
end
- it 'should filter by assignee id' do
- params = { scope: "all", assignee_id: user.id, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(2)
+ context 'filtering by assignee ID' do
+ let(:params) { { assignee_id: user.id } }
+
+ it 'returns issues assigned to that user' do
+ expect(issues).to contain_exactly(issue1, issue2)
+ end
end
- it 'should filter by author id' do
- params = { scope: "all", author_id: user2.id, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue3])
+ context 'filtering by author ID' do
+ let(:params) { { author_id: user2.id } }
+
+ it 'returns issues created by that user' do
+ expect(issues).to contain_exactly(issue3)
+ end
end
- it 'should filter by milestone id' do
- params = { scope: "all", milestone_title: milestone.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue1])
+ context 'filtering by milestone' do
+ let(:params) { { milestone_title: milestone.title } }
+
+ it 'returns issues assigned to that milestone' do
+ expect(issues).to contain_exactly(issue1)
+ end
end
- it 'should filter by no milestone id' do
- params = { scope: "all", milestone_title: Milestone::None.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to match_array([issue2, issue3])
+ context 'filtering by no milestone' do
+ let(:params) { { milestone_title: Milestone::None.title } }
+
+ it 'returns issues with no milestone' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
end
- it 'should filter by label name' do
- params = { scope: "all", label_name: label.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to eq([issue2])
+ context 'filtering by upcoming milestone' do
+ let(:params) { { milestone_title: Milestone::Upcoming.name } }
+
+ let(:project_no_upcoming_milestones) { create(:empty_project, :public) }
+ let(:project_next_1_1) { create(:empty_project, :public) }
+ let(:project_next_8_8) { create(:empty_project, :public) }
+
+ let(:yesterday) { Date.today - 1.day }
+ let(:tomorrow) { Date.today + 1.day }
+ let(:two_days_from_now) { Date.today + 2.days }
+ let(:ten_days_from_now) { Date.today + 10.days }
+
+ let(:milestones) do
+ [
+ create(:milestone, :closed, project: project_no_upcoming_milestones),
+ create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
+ create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now),
+ create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday),
+ create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow)
+ ]
+ end
+
+ before do
+ milestones.each do |milestone|
+ create(:issue, project: milestone.project, milestone: milestone, author: user, assignee: user)
+ end
+ end
+
+ it 'returns issues in the upcoming milestone for each project' do
+ expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8')
+ expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now)
+ end
end
- it 'returns unique issues when filtering by multiple labels' do
- label2 = create(:label, project: project2)
+ context 'filtering by label' do
+ let(:params) { { label_name: label.title } }
- create(:label_link, label: label2, target: issue2)
+ it 'returns issues with that label' do
+ expect(issues).to contain_exactly(issue2)
+ end
+ end
- params = {
- scope: 'all',
- label_name: [label.title, label2.title].join(','),
- state: 'opened'
- }
+ context 'filtering by multiple labels' do
+ let(:params) { { label_name: [label.title, label2.title].join(',') } }
+ let(:label2) { create(:label, project: project2) }
- issues = IssuesFinder.new(user, params).execute
+ before { create(:label_link, label: label2, target: issue2) }
- expect(issues).to eq([issue2])
+ it 'returns the unique issues with any of those labels' do
+ expect(issues).to contain_exactly(issue2)
+ end
end
- it 'should filter by no label name' do
- params = { scope: "all", label_name: Label::None.title, state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues).to match_array([issue1, issue3])
+ context 'filtering by no label' do
+ let(:params) { { label_name: Label::None.title } }
+
+ it 'returns issues with no labels' do
+ expect(issues).to contain_exactly(issue1, issue3)
+ end
end
- it 'should be empty for unauthorized user' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(nil, params).execute
- expect(issues.size).to be_zero
+ context 'when the user is unauthorized' do
+ let(:search_user) { nil }
+
+ it 'returns no results' do
+ expect(issues).to be_empty
+ end
end
- it 'should not include unauthorized issues' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new(user2, params).execute
- expect(issues.size).to eq(2)
- expect(issues).not_to include(issue1)
- expect(issues).to include(issue2)
- expect(issues).to include(issue3)
+ context 'when the user can see some, but not all, issues' do
+ let(:search_user) { user2 }
+
+ it 'returns only issues they can see' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
end
end
context 'personal scope' do
- it 'should filter by assignee' do
- params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(2)
+ let(:scope) { 'assigned-to-me' }
+
+ it 'returns issue assigned to the user' do
+ expect(issues).to contain_exactly(issue1, issue2)
end
- it 'should filter by project' do
- params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new(user, params).execute
- expect(issues.size).to eq(1)
+ context 'filtering by project' do
+ let(:params) { { project_id: project1.id } }
+
+ it 'returns issues assigned to the user in that project' do
+ expect(issues).to contain_exactly(issue1)
+ end
end
end
end
diff --git a/spec/fixtures/container_registry/config_blob.json b/spec/fixtures/container_registry/config_blob.json
new file mode 100644
index 00000000000..1028c994a24
--- /dev/null
+++ b/spec/fixtures/container_registry/config_blob.json
@@ -0,0 +1 @@
+{"architecture":"amd64","config":{"Hostname":"b14cd8298755","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"b14cd82987550b01af9a666a2f4c996280a6152e66873134fae5a0f223dc5976","container_config":{"Hostname":"b14cd8298755","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","#(nop) ADD file:033ab063740d9ff4dcfb1c69eccf25f91d88729f57cd5a73050e014e3e094aa0 in /"],"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2016-04-01T20:53:00.160300546Z","docker_version":"1.9.1","history":[{"created":"2016-04-01T20:53:00.160300546Z","created_by":"/bin/sh -c #(nop) ADD file:033ab063740d9ff4dcfb1c69eccf25f91d88729f57cd5a73050e014e3e094aa0 in /"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:c56b7dabbc7aa730eeab07668bdcbd7e3d40855047ca9a0cc1bfed23a2486111"]}}
diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json
new file mode 100644
index 00000000000..1b6008e2872
--- /dev/null
+++ b/spec/fixtures/container_registry/tag_manifest.json
@@ -0,0 +1 @@
+{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]}
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 1772cc3f6a4..34ce7c4f033 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -216,10 +216,14 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
#### MilestoneReferenceFilter
-- Milestone: <%= milestone.to_reference %>
+- Milestone by ID: <%= simple_milestone.to_reference %>
+- Milestone by name: <%= Milestone.reference_prefix %><%= simple_milestone.name %>
+- Milestone by name in quotes: <%= milestone.to_reference(format: :name) %>
- Milestone in another project: <%= xmilestone.to_reference(project) %>
-- Ignored in code: `<%= milestone.to_reference %>`
-- Link to milestone by URL: [Milestone](<%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>)
+- Ignored in code: `<%= simple_milestone.to_reference %>`
+- Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link)
+- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>
+- Link to milestone by URL: [Milestone](<%= milestone.to_reference %>)
### Task Lists
@@ -239,3 +243,16 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- [[link-text|http://example.com/pdfs/gollum.pdf]]
- [[images/example.jpg]]
- [[http://example.com/images/example.jpg]]
+
+### Inline Diffs
+
+With inline diffs tags you can display {+ additions +} or [- deletions -].
+
+The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
+
+However the wrapping tags can not be mixed as such -
+
+- {+ additions +]
+- [+ additions +}
+- {- delletions -]
+- [- delletions -}
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index e47a54fdac5..49ea4fa6d3e 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe AuthHelper do
describe "button_based_providers" do
- it 'returns all enabled providers' do
+ it 'returns all enabled providers from devise' do
allow(helper).to receive(:auth_providers) { [:twitter, :github] }
expect(helper.button_based_providers).to include(*[:twitter, :github])
end
@@ -17,4 +17,49 @@ describe AuthHelper do
expect(helper.button_based_providers).to eq([])
end
end
+
+ describe 'enabled_button_based_providers' do
+ before do
+ allow(helper).to receive(:auth_providers) { [:twitter, :github] }
+ end
+
+ context 'all providers are enabled to sign in' do
+ it 'returns all the enabled providers from settings' do
+ expect(helper.enabled_button_based_providers).to include('twitter', 'github')
+ end
+ end
+
+ context 'GitHub OAuth sign in is disabled from application setting' do
+ it "doesn't return github as provider" do
+ stub_application_setting(
+ disabled_oauth_sign_in_sources: ['github']
+ )
+
+ expect(helper.enabled_button_based_providers).to include('twitter')
+ expect(helper.enabled_button_based_providers).not_to include('github')
+ end
+ end
+ end
+
+ describe 'button_based_providers_enabled?' do
+ before do
+ allow(helper).to receive(:auth_providers) { [:twitter, :github] }
+ end
+
+ context 'button based providers enabled' do
+ it 'returns true' do
+ expect(helper.button_based_providers_enabled?).to be true
+ end
+ end
+
+ context 'all the button based providers are disabled via application_setting' do
+ it 'returns false' do
+ stub_application_setting(
+ disabled_oauth_sign_in_sources: ['github', 'twitter']
+ )
+
+ expect(helper.button_based_providers_enabled?).to be false
+ end
+ end
+ end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index b7810185d16..52764f41e0d 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -93,9 +93,9 @@ describe DiffHelper do
it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
- expect(marked_old_line).to eq("abc <span class='idiff left right'>&#39;def&#39;</span>")
+ expect(marked_old_line).to eq("abc <span class='idiff left right deletion'>&#39;def&#39;</span>")
expect(marked_old_line).to be_html_safe
- expect(marked_new_line).to eq("abc <span class='idiff left right'>&quot;def&quot;</span>")
+ expect(marked_new_line).to eq("abc <span class='idiff left right addition'>&quot;def&quot;</span>")
expect(marked_new_line).to be_html_safe
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index e68a5ec29ab..c0d2be98e85 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -1,64 +1,65 @@
require 'spec_helper'
describe EventsHelper do
- include ApplicationHelper
- include GitlabMarkdownHelper
+ describe '#event_note' do
+ before do
+ allow(helper).to receive(:current_user).and_return(double)
+ end
- let(:current_user) { create(:user, email: "current@email.com") }
+ it 'should display one line of plain text without alteration' do
+ input = 'A short, plain note'
+ expect(helper.event_note(input)).to match(input)
+ expect(helper.event_note(input)).not_to match(/\.\.\.\z/)
+ end
- it 'should display one line of plain text without alteration' do
- input = 'A short, plain note'
- expect(event_note(input)).to match(input)
- expect(event_note(input)).not_to match(/\.\.\.\z/)
- end
+ it 'should display inline code' do
+ input = 'A note with `inline code`'
+ expected = 'A note with <code>inline code</code>'
- it 'should display inline code' do
- input = 'A note with `inline code`'
- expected = 'A note with <code>inline code</code>'
+ expect(helper.event_note(input)).to match(expected)
+ end
- expect(event_note(input)).to match(expected)
- end
+ it 'should truncate a note with multiple paragraphs' do
+ input = "Paragraph 1\n\nParagraph 2"
+ expected = 'Paragraph 1...'
- it 'should truncate a note with multiple paragraphs' do
- input = "Paragraph 1\n\nParagraph 2"
- expected = 'Paragraph 1...'
+ expect(helper.event_note(input)).to match(expected)
+ end
- expect(event_note(input)).to match(expected)
- end
+ it 'should display the first line of a code block' do
+ input = "```\nCode block\nwith two lines\n```"
+ expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
- it 'should display the first line of a code block' do
- input = "```\nCode block\nwith two lines\n```"
- expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
+ expect(helper.event_note(input)).to match(expected)
+ end
- expect(event_note(input)).to match(expected)
- end
+ it 'should truncate a single long line of text' do
+ text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
+ input = text * 4
+ expected = (text * 2).sub(/.{3}/, '...')
- it 'should truncate a single long line of text' do
- text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
- input = "#{text}#{text}#{text}#{text}" # 200 chars
- expected = "#{text}#{text}".sub(/.{3}/, '...')
+ expect(helper.event_note(input)).to match(expected)
+ end
- expect(event_note(input)).to match(expected)
- end
-
- it 'should preserve a link href when link text is truncated' do
- text = 'The quick brown fox jumped over the lazy dog' # 44 chars
- input = "#{text}#{text}#{text} " # 133 chars
- link_url = 'http://example.com/foo/bar/baz' # 30 chars
- input << link_url
- expected_link_text = 'http://example...</a>'
+ it 'should preserve a link href when link text is truncated' do
+ text = 'The quick brown fox jumped over the lazy dog' # 44 chars
+ input = "#{text}#{text}#{text} " # 133 chars
+ link_url = 'http://example.com/foo/bar/baz' # 30 chars
+ input << link_url
+ expected_link_text = 'http://example...</a>'
- expect(event_note(input)).to match(link_url)
- expect(event_note(input)).to match(expected_link_text)
- end
+ expect(helper.event_note(input)).to match(link_url)
+ expect(helper.event_note(input)).to match(expected_link_text)
+ end
- it 'should preserve code color scheme' do
- input = "```ruby\ndef test\n 'hello world'\nend\n```"
- expected = '<pre class="code highlight js-syntax-highlight ruby">' \
- "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
- " <span class=\"s1\">\'hello world\'</span>\n" \
- "<span class=\"k\">end</span>" \
- '</code></pre>'
- expect(event_note(input)).to eq(expected)
+ it 'should preserve code color scheme' do
+ input = "```ruby\ndef test\n 'hello world'\nend\n```"
+ expected = '<pre class="code highlight js-syntax-highlight ruby">' \
+ "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
+ " <span class=\"s1\">\'hello world\'</span>\n" \
+ "<span class=\"k\">end</span>" \
+ '</code></pre>'
+ expect(helper.event_note(input)).to eq(expected)
+ end
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 600e1c4e9ec..8e7ed42e883 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -17,7 +17,7 @@ describe MergeRequestsHelper do
it 'does not include api credentials in a link' do
allow(ci_service).
to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
- expect(helper.ci_build_details_path(merge_request)).to_not match("secret")
+ expect(helper.ci_build_details_path(merge_request)).not_to match("secret")
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 29bcb8c5892..ac5af8740dc 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -88,18 +88,18 @@ describe ProjectsHelper do
end
describe 'default_clone_protocol' do
- describe 'using HTTP' do
+ context 'when user is not logged in and gitlab protocol is HTTP' do
it 'returns HTTP' do
- expect(helper).to receive(:current_user).and_return(nil)
+ allow(helper).to receive(:current_user).and_return(nil)
expect(helper.send(:default_clone_protocol)).to eq('http')
end
end
- describe 'using HTTPS' do
+ context 'when user is not logged in and gitlab protocol is HTTPS' do
it 'returns HTTPS' do
- allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https')
- expect(helper).to receive(:current_user).and_return(nil)
+ stub_config_setting(protocol: 'https')
+ allow(helper).to receive(:current_user).and_return(nil)
expect(helper.send(:default_clone_protocol)).to eq('https')
end
diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml
new file mode 100644
index 00000000000..95efaff4b69
--- /dev/null
+++ b/spec/javascripts/fixtures/right_sidebar.html.haml
@@ -0,0 +1,13 @@
+%div
+ %div.page-gutter.page-with-sidebar
+
+ %aside.right-sidebar
+ %div.block.issuable-sidebar-header
+ %a.gutter-toggle.pull-right.js-sidebar-toggle
+ %i.fa.fa-angle-double-left
+
+ %form.issuable-context-form
+ %div.block.labels
+ %div.sidebar-collapsed-icon
+ %i.fa.fa-tags
+ %span 1
diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
index 78d39f1b428..82ee1954a59 100644
--- a/spec/javascripts/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
@@ -1,4 +1,4 @@
-//= require stat_graph_contributors_graph
+//= require graphs/stat_graph_contributors_graph
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index dbafe782b77..5b992447473 100644
--- a/spec/javascripts/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -1,4 +1,4 @@
-//= require stat_graph_contributors_util
+//= require graphs/stat_graph_contributors_util
describe("ContributorsStatGraphUtil", function () {
diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js
index 4c652910cd6..4b05d401a42 100644
--- a/spec/javascripts/stat_graph_spec.js
+++ b/spec/javascripts/graphs/stat_graph_spec.js
@@ -1,4 +1,4 @@
-//= require stat_graph
+//= require graphs/stat_graph
describe("StatGraph", function () {
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
index 3d8de2ff989..1cf34d4d2d3 100644
--- a/spec/javascripts/project_title_spec.js.coffee
+++ b/spec/javascripts/project_title_spec.js.coffee
@@ -1,5 +1,6 @@
#= require bootstrap
#= require select2
+#= require lib/type_utility
#= require gl_dropdown
#= require api
#= require project_select
diff --git a/spec/javascripts/right_sidebar_spec.js.coffee b/spec/javascripts/right_sidebar_spec.js.coffee
new file mode 100644
index 00000000000..2075cacdb67
--- /dev/null
+++ b/spec/javascripts/right_sidebar_spec.js.coffee
@@ -0,0 +1,69 @@
+#= require right_sidebar
+#= require jquery
+#= require jquery.cookie
+
+@sidebar = null
+$aside = null
+$toggle = null
+$icon = null
+$page = null
+$labelsIcon = null
+
+
+assertSidebarState = (state) ->
+
+ shouldBeExpanded = state is 'expanded'
+ shouldBeCollapsed = state is 'collapsed'
+
+ expect($aside.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
+ expect($page.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
+ expect($icon.hasClass('fa-angle-double-right')).toBe shouldBeExpanded
+
+ expect($aside.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
+ expect($page.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
+ expect($icon.hasClass('fa-angle-double-left')).toBe shouldBeCollapsed
+
+
+describe 'RightSidebar', ->
+
+ fixture.preload 'right_sidebar.html'
+
+ beforeEach ->
+ fixture.load 'right_sidebar.html'
+
+ @sidebar = new Sidebar
+ $aside = $ '.right-sidebar'
+ $page = $ '.page-with-sidebar'
+ $icon = $aside.find 'i'
+ $toggle = $aside.find '.js-sidebar-toggle'
+ $labelsIcon = $aside.find '.sidebar-collapsed-icon'
+
+
+ it 'should expand the sidebar when arrow is clicked', ->
+
+ $toggle.click()
+ assertSidebarState 'expanded'
+
+
+ it 'should collapse the sidebar when arrow is clicked', ->
+
+ $toggle.click()
+ assertSidebarState 'expanded'
+
+ $toggle.click()
+ assertSidebarState 'collapsed'
+
+
+ it 'should float over the page and when sidebar icons clicked', ->
+
+ $labelsIcon.click()
+ assertSidebarState 'expanded'
+
+
+ it 'should collapse when the icon arrow clicked while it is floating on page', ->
+
+ $labelsIcon.click()
+ assertSidebarState 'expanded'
+
+ $toggle.click()
+ assertSidebarState 'collapsed'
diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/award_emoji_spec.rb
index 88c22912950..c3098574292 100644
--- a/spec/lib/award_emoji_spec.rb
+++ b/spec/lib/award_emoji_spec.rb
@@ -5,7 +5,7 @@ describe AwardEmoji do
subject { AwardEmoji.urls }
it { is_expected.to be_an_instance_of(Array) }
- it { is_expected.to_not be_empty }
+ it { is_expected.not_to be_empty }
context 'every Hash in the Array' do
it 'has the correct keys and values' do
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index c2a8ad36c30..593bd6d5cac 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -98,11 +98,6 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit_range]).not_to be_empty
- end
end
context 'cross-project reference' do
@@ -135,11 +130,6 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit_range]).not_to be_empty
- end
end
context 'cross-project URL reference' do
@@ -173,10 +163,5 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit_range]).not_to be_empty
- end
end
end
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 63a32d9d455..d46d3f1489e 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -93,11 +93,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
end
context 'cross-project reference' do
@@ -124,11 +119,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
exp = act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
end
context 'cross-project URL reference' do
@@ -154,10 +144,5 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
end
end
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
new file mode 100644
index 00000000000..9e526371294
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe Banzai::Filter::InlineDiffFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'adds inline diff span tags for deletions when using square brackets' do
+ doc = "START [-something deleted-] END"
+ expect(filter(doc).to_html).to eq('START <span class="idiff left right deletion">something deleted</span> END')
+ end
+
+ it 'adds inline diff span tags for deletions when using curley braces' do
+ doc = "START {-something deleted-} END"
+ expect(filter(doc).to_html).to eq('START <span class="idiff left right deletion">something deleted</span> END')
+ end
+
+ it 'does not add inline diff span tags when a closing tag is not provided' do
+ doc = "START [- END"
+ expect(filter(doc).to_html).to eq(doc)
+ end
+
+ it 'adds inline span tags for additions when using square brackets' do
+ doc = "START [+something added+] END"
+ expect(filter(doc).to_html).to eq('START <span class="idiff left right addition">something added</span> END')
+ end
+
+ it 'adds inline span tags for additions when using curley braces' do
+ doc = "START {+something added+} END"
+ expect(filter(doc).to_html).to eq('START <span class="idiff left right addition">something added</span> END')
+ end
+
+ it 'does not add inline diff span tags when a closing addition tag is not provided' do
+ doc = "START {+ END"
+ expect(filter(doc).to_html).to eq(doc)
+ end
+
+ it 'does not add inline diff span tags when the tags do not match' do
+ examples = [
+ "{+ additions +]",
+ "[+ additions +}",
+ "{- delletions -]",
+ "[- delletions -}"
+ ]
+
+ examples.each do |doc|
+ expect(filter(doc).to_html).to eq(doc)
+ end
+ end
+
+ it 'prevents user-land html being injected' do
+ doc = "START {+&lt;script&gt;alert('I steal cookies')&lt;/script&gt;+} END"
+ expect(filter(doc).to_html).to eq("START <span class=\"idiff left right addition\">&lt;script&gt;alert('I steal cookies')&lt;/script&gt;</span> END")
+ end
+
+ it 'preserves content inside pre tags' do
+ doc = "<pre>START {+something added+} END</pre>"
+ expect(filter(doc).to_html).to eq(doc)
+ end
+
+ it 'preserves content inside code tags' do
+ doc = "<code>START {+something added+} END</code>"
+ expect(filter(doc).to_html).to eq(doc)
+ end
+
+ it 'preserves content inside tt tags' do
+ doc = "<tt>START {+something added+} END</tt>"
+ expect(filter(doc).to_html).to eq(doc)
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 266ebef33d6..8e6a264970d 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -91,11 +91,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
end
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
-
it 'does not process links containing issue numbers followed by text' do
href = "#{reference}st"
doc = reference_filter("<a href='#{href}'></a>")
@@ -136,11 +131,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
end
context 'cross-project URL reference' do
@@ -160,11 +150,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
end
context 'cross-project reference in link href' do
@@ -184,11 +169,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
end
context 'cross-project URL in link href' do
@@ -208,10 +188,5 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index b0a38e7c251..f1064a701d8 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -48,11 +48,6 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
end
- it 'adds to the results hash' do
- result = reference_pipeline_result("Label #{reference}")
- expect(result[:references][:label]).to eq [label]
- end
-
describe 'label span element' do
it 'includes default classes' do
doc = reference_filter("Label #{reference}")
@@ -170,11 +165,6 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
expect(link).to have_attribute('data-label')
expect(link.attr('data-label')).to eq label.id.to_s
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Label #{reference}")
- expect(result[:references][:label]).to eq [label]
- end
end
describe 'cross project label references' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 352710df307..3185e41fe5c 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -78,11 +78,6 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
end
context 'cross-project reference' do
@@ -109,11 +104,6 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
end
context 'cross-project URL reference' do
@@ -133,10 +123,5 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
end
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index ebf3d7489b5..9424f2363e1 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -3,8 +3,9 @@ require 'spec_helper'
describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
include FilterSpecHelper
- let(:project) { create(:project, :public) }
- let(:milestone) { create(:milestone, project: project) }
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:reference) { milestone.to_reference }
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
@@ -17,11 +18,37 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
end
end
- context 'internal reference' do
- # Convert the Markdown link to only the URL, since these tests aren't run through the regular Markdown pipeline.
- # Milestone reference behavior in the full Markdown pipeline is tested elsewhere.
- let(:reference) { milestone.to_reference.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\2') }
+ it 'includes default classes' do
+ doc = reference_filter("Milestone #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Milestone #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-milestone attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-milestone')
+ expect(link.attr('data-milestone')).to eq milestone.id.to_s
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Milestone #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.
+ namespace_project_milestone_path(project.namespace, project, milestone)
+ end
+
+ context 'Integer-based references' do
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -30,29 +57,82 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
end
it 'links with adjacent text' do
- doc = reference_filter("milestone (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(milestone.title)}<\/a>\.\)/)
+ doc = reference_filter("Milestone (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
end
- it 'includes a title attribute' do
- doc = reference_filter("milestone #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Milestone: #{milestone.title}"
+ it 'ignores invalid milestone IIDs' do
+ exp = act = "Milestone #{invalidate_reference(reference)}"
+
+ expect(reference_filter(act).to_html).to eq exp
end
+ end
+
+ context 'String-based single-word references' do
+ let(:milestone) { create(:milestone, name: 'gfm', project: project) }
+ let(:reference) { "#{Milestone.reference_prefix}#{milestone.name}" }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
- it 'escapes the title attribute' do
- milestone.update_attribute(:title, %{"></a>whatever<a title="})
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(project.namespace, project, milestone)
+ expect(doc.text).to eq 'See gfm'
+ end
- doc = reference_filter("milestone #{reference}")
- expect(doc.text).to eq "milestone #{milestone.title}"
+ it 'links with adjacent text' do
+ doc = reference_filter("Milestone (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
end
- it 'includes default classes' do
- doc = reference_filter("milestone #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+ it 'ignores invalid milestone names' do
+ exp = act = "Milestone #{Milestone.reference_prefix}#{milestone.name.reverse}"
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'String-based multi-word references in quotes' do
+ let(:milestone) { create(:milestone, name: 'gfm references', project: project) }
+ let(:reference) { milestone.to_reference(format: :name) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(project.namespace, project, milestone)
+ expect(doc.text).to eq 'See gfm references'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Milestone (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
+ end
+
+ it 'ignores invalid milestone names' do
+ exp = act = %(Milestone #{Milestone.reference_prefix}"#{milestone.name.reverse}")
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ describe 'referencing a milestone in a link href' do
+ let(:reference) { %Q{<a href="#{milestone.to_reference}">Milestone</a>} }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(project.namespace, project, milestone)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Milestone (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+>Milestone</a>\.\)))
end
it 'includes a data-project attribute' do
- doc = reference_filter("milestone #{reference}")
+ doc = reference_filter("Milestone #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
@@ -66,10 +146,31 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
expect(link).to have_attribute('data-milestone')
expect(link.attr('data-milestone')).to eq milestone.id.to_s
end
+ end
+
+ describe 'cross project milestone references' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_path) { another_project.path_with_namespace }
+ let(:milestone) { create(:milestone, project: another_project) }
+ let(:reference) { milestone.to_reference(project) }
+
+ let!(:result) { reference_filter("See #{reference}") }
+
+ it 'points to referenced project milestone page' do
+ expect(result.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(another_project.namespace,
+ another_project,
+ milestone)
+ end
- it 'adds to the results hash' do
- result = reference_pipeline_result("milestone #{reference}")
- expect(result[:references][:milestone]).to eq [milestone]
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}"
+ end
+
+ it 'escapes the name attribute' do
+ allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="})
+ doc = reference_filter("See #{reference}")
+ expect(doc.css('a').first.text).to eq "#{milestone.name} in #{project_path}"
end
end
end
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index c2c2fd0eb6a..697d10bbf70 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -16,11 +16,23 @@ describe Banzai::Filter::RedactorFilter, lib: true do
end
context 'with data-project' do
+ let(:parser_class) do
+ Class.new(Banzai::ReferenceParser::BaseParser) do
+ self.reference_type = :test
+ end
+ end
+
+ before do
+ allow(Banzai::ReferenceParser).to receive(:[]).
+ with('test').
+ and_return(parser_class)
+ end
+
it 'removes unpermitted Project references' do
user = create(:user)
project = create(:empty_project)
- link = reference_link(project: project.id, reference_filter: 'ReferenceFilter')
+ link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
@@ -31,14 +43,14 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project)
project.team << [user, :master]
- link = reference_link(project: project.id, reference_filter: 'ReferenceFilter')
+ link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Project references' do
- link = reference_link(project: 12345, reference_filter: 'ReferenceFilter')
+ link = reference_link(project: 12345, reference_type: 'test')
expect { filter(link) }.not_to raise_error
end
@@ -51,7 +63,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: non_member)
expect(doc.css('a').length).to eq 0
@@ -62,7 +74,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project, author: author)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: author)
expect(doc.css('a').length).to eq 1
@@ -73,7 +85,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project, assignee: assignee)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 1
@@ -85,7 +97,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project.team << [member, :developer]
issue = create(:issue, :confidential, project: project)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1
@@ -96,7 +108,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project, :public)
issue = create(:issue, :confidential, project: project)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: admin)
expect(doc.css('a').length).to eq 1
@@ -108,7 +120,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
project = create(:empty_project, :public)
issue = create(:issue, project: project)
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
@@ -121,7 +133,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
user = create(:user)
group = create(:group, :private)
- link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+ link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
@@ -132,14 +144,14 @@ describe Banzai::Filter::RedactorFilter, lib: true do
group = create(:group, :private)
group.add_developer(user)
- link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+ link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Group references' do
- link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter')
+ link = reference_link(group: 12345, reference_type: 'user')
expect { filter(link) }.not_to raise_error
end
@@ -149,7 +161,7 @@ describe Banzai::Filter::RedactorFilter, lib: true do
it 'allows any User reference' do
user = create(:user)
- link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter')
+ link = reference_link(user: user.id, reference_type: 'user')
doc = filter(link)
expect(doc.css('a').length).to eq 1
diff --git a/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb b/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb
deleted file mode 100644
index c8b1dfdf944..00000000000
--- a/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::ReferenceGathererFilter, lib: true do
- include ActionView::Helpers::UrlHelper
- include FilterSpecHelper
-
- def reference_link(data)
- link_to('text', '', class: 'gfm', data: data)
- end
-
- context "for issue references" do
-
- context 'with data-project' do
- it 'removes unpermitted Project references' do
- user = create(:user)
- project = create(:empty_project)
- issue = create(:issue, project: project)
-
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:issue]).to be_empty
- end
-
- it 'allows permitted Project references' do
- user = create(:user)
- project = create(:empty_project)
- issue = create(:issue, project: project)
- project.team << [user, :master]
-
- link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:issue]).to eq([issue])
- end
-
- it 'handles invalid Project references' do
- link = reference_link(project: 12345, issue: 12345, reference_filter: 'IssueReferenceFilter')
-
- expect { pipeline_result(link) }.not_to raise_error
- end
- end
- end
-
- context "for user references" do
-
- context 'with data-group' do
- it 'removes unpermitted Group references' do
- user = create(:user)
- group = create(:group)
-
- link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:user]).to be_empty
- end
-
- it 'allows permitted Group references' do
- user = create(:user)
- group = create(:group)
- group.add_developer(user)
-
- link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:user]).to eq([user])
- end
-
- it 'handles invalid Group references' do
- link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter')
-
- expect { pipeline_result(link) }.not_to raise_error
- end
- end
-
- context 'with data-user' do
- it 'allows any User reference' do
- user = create(:user)
-
- link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter')
- result = pipeline_result(link)
-
- expect(result[:references][:user]).to eq([user])
- end
- end
- end
-end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 27ce312b11c..b38e3b17e64 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -22,6 +22,12 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'sanitizes mixed-cased javascript in attributes' do
+ act = %q(<a href="javaScript:alert('foo')">Text</a>)
+ exp = '<a>Text</a>'
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'allows whitelisted HTML tags from the user' do
exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
expect(filter(act).to_html).to eq exp
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 26466fbb180..5068ddd7faa 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -77,11 +77,6 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
end
context 'cross-project reference' do
@@ -107,11 +102,6 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
end
context 'cross-project URL reference' do
@@ -137,10 +127,5 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
end
end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 3b073a90a95..b83be54746c 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -8,6 +8,10 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
project: project
})
+ raw_filter(doc, contexts)
+ end
+
+ def raw_filter(doc, contexts = {})
described_class.call(doc, contexts)
end
@@ -70,4 +74,18 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
end
end
+
+ context 'when project context does not exist' do
+ let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
+
+ it 'does not raise error' do
+ expect { raw_filter(upload_link, project: nil) }.not_to raise_error
+ end
+
+ it 'does not rewrite link' do
+ doc = raw_filter(upload_link, project: nil)
+
+ expect(doc.to_html).to eq upload_link
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 8bdebae1841..d7dfd6699ef 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -31,28 +31,22 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
it 'supports a special @all mention' do
- doc = reference_filter("Hey #{reference}")
+ doc = reference_filter("Hey #{reference}", author: user)
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
end
- context "when the author is a member of the project" do
+ it 'includes a data-author attribute when there is an author' do
+ doc = reference_filter(reference, author: user)
- it 'adds to the results hash' do
- result = reference_pipeline_result("Hey #{reference}", author: project.creator)
- expect(result[:references][:user]).to eq [project.creator]
- end
+ expect(doc.css('a').first.attr('data-author')).to eq(user.id.to_s)
end
- context "when the author is not a member of the project" do
-
- let(:other_user) { create(:user) }
+ it 'does not include a data-author attribute when there is no author' do
+ doc = reference_filter(reference)
- it "doesn't add to the results hash" do
- result = reference_pipeline_result("Hey #{reference}", author: other_user)
- expect(result[:references][:user]).to eq []
- end
+ expect(doc.css('a').first.has_attribute?('data-author')).to eq(false)
end
end
@@ -83,11 +77,6 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(link).to have_attribute('data-user')
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Hey #{reference}")
- expect(result[:references][:user]).to eq [user]
- end
end
context 'mentioning a group' do
@@ -106,11 +95,6 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(link).to have_attribute('data-group')
expect(link.attr('data-group')).to eq group.id.to_s
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Hey #{reference}")
- expect(result[:references][:user]).to eq group.users
- end
end
it 'links with adjacent text' do
@@ -151,10 +135,5 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(link).to have_attribute('data-user')
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Hey #{reference}")
- expect(result[:references][:user]).to eq [user]
- end
end
end
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
new file mode 100644
index 00000000000..185abbb2108
--- /dev/null
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Banzai::Filter::WikiLinkFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
+ let(:project) { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
+ let(:user) { double }
+ let(:project_wiki) { ProjectWiki.new(project, user) }
+
+ describe "links within the wiki (relative)" do
+ describe "hierarchical links to the current directory" do
+ it "doesn't rewrite non-file links" do
+ link = "<a href='./page'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('./page')
+ end
+
+ it "doesn't rewrite file links" do
+ link = "<a href='./page.md'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('./page.md')
+ end
+ end
+
+ describe "hierarchical links to the parent directory" do
+ it "doesn't rewrite non-file links" do
+ link = "<a href='../page'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('../page')
+ end
+
+ it "doesn't rewrite file links" do
+ link = "<a href='../page.md'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('../page.md')
+ end
+ end
+
+ describe "hierarchical links to a sub-directory" do
+ it "doesn't rewrite non-file links" do
+ link = "<a href='./subdirectory/page'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('./subdirectory/page')
+ end
+
+ it "doesn't rewrite file links" do
+ link = "<a href='./subdirectory/page.md'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md')
+ end
+ end
+
+ describe "non-hierarchical links" do
+ it 'rewrites non-file links to be at the scope of the wiki root' do
+ link = "<a href='page'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to match('/wiki_link_ns/wiki_link_project/wikis/page')
+ end
+
+ it "doesn't rewrite file links" do
+ link = "<a href='page.md'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('page.md')
+ end
+ end
+ end
+
+ describe "links outside the wiki (absolute)" do
+ it "doesn't rewrite links" do
+ link = "<a href='http://example.com/page'>Link to Page</a>"
+ filtered_link = filter(link, project_wiki: project_wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('http://example.com/page')
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
new file mode 100644
index 00000000000..543b4786d84
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -0,0 +1,237 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::BaseParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :public) }
+
+ subject do
+ klass = Class.new(described_class) do
+ self.reference_type = :foo
+ end
+
+ klass.new(project, user)
+ end
+
+ describe '.reference_type=' do
+ it 'sets the reference type' do
+ dummy = Class.new(described_class)
+ dummy.reference_type = :foo
+
+ expect(dummy.reference_type).to eq(:foo)
+ end
+ end
+
+ describe '#nodes_visible_to_user' do
+ let(:link) { empty_html_link }
+
+ context 'when the link has a data-project attribute' do
+ it 'returns the nodes if the attribute value equals the current project ID' do
+ link['data-project'] = project.id.to_s
+
+ expect(Ability.abilities).not_to receive(:allowed?)
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns the nodes if the user can read the project' do
+ other_project = create(:empty_project, :public)
+
+ link['data-project'] = other_project.id.to_s
+
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_project, other_project).
+ and_return(true)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns an empty Array when the attribute value is empty' do
+ link['data-project'] = ''
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+
+ it 'returns an empty Array when the user can not read the project' do
+ other_project = create(:empty_project, :public)
+
+ link['data-project'] = other_project.id.to_s
+
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_project, other_project).
+ and_return(false)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns the nodes' do
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+ end
+ end
+
+ describe '#nodes_user_can_reference' do
+ it 'returns the nodes' do
+ link = double(:link)
+
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([link])
+ end
+ end
+
+ describe '#referenced_by' do
+ context 'when references_relation is implemented' do
+ it 'returns a collection of objects' do
+ links = Nokogiri::HTML.fragment("<a data-foo='#{user.id}'></a>").
+ children
+
+ expect(subject).to receive(:references_relation).and_return(User)
+ expect(subject.referenced_by(links)).to eq([user])
+ end
+ end
+
+ context 'when references_relation is not implemented' do
+ it 'raises NotImplementedError' do
+ links = Nokogiri::HTML.fragment('<a data-foo="1"></a>').children
+
+ expect { subject.referenced_by(links) }.
+ to raise_error(NotImplementedError)
+ end
+ end
+ end
+
+ describe '#references_relation' do
+ it 'raises NotImplementedError' do
+ expect { subject.references_relation }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#gather_attributes_per_project' do
+ it 'returns a Hash containing attribute values per project' do
+ link = Nokogiri::HTML.fragment('<a data-project="1" data-foo="2"></a>').
+ children[0]
+
+ hash = subject.gather_attributes_per_project([link], 'data-foo')
+
+ expect(hash).to be_an_instance_of(Hash)
+
+ expect(hash[1].to_a).to eq(['2'])
+ end
+ end
+
+ describe '#grouped_objects_for_nodes' do
+ it 'returns a Hash grouping objects per ID' do
+ nodes = [double(:node)]
+
+ expect(subject).to receive(:unique_attribute_values).
+ with(nodes, 'data-user').
+ and_return([user.id])
+
+ hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user')
+
+ expect(hash).to eq({ user.id => user })
+ end
+
+ it 'returns an empty Hash when the list of nodes is empty' do
+ expect(subject.grouped_objects_for_nodes([], User, 'data-user')).to eq({})
+ end
+ end
+
+ describe '#unique_attribute_values' do
+ it 'returns an Array of unique values' do
+ link = double(:link)
+
+ expect(link).to receive(:has_attribute?).
+ with('data-foo').
+ twice.
+ and_return(true)
+
+ expect(link).to receive(:attr).
+ with('data-foo').
+ twice.
+ and_return('1')
+
+ nodes = [link, link]
+
+ expect(subject.unique_attribute_values(nodes, 'data-foo')).to eq(['1'])
+ end
+ end
+
+ describe '#process' do
+ it 'gathers the references for every node matching the reference type' do
+ dummy = Class.new(described_class) do
+ self.reference_type = :test
+ end
+
+ instance = dummy.new(project, user)
+ document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>')
+
+ expect(instance).to receive(:gather_references).
+ with([document.children[1]]).
+ and_return([user])
+
+ expect(instance.process([document])).to eq([user])
+ end
+ end
+
+ describe '#gather_references' do
+ let(:link) { double(:link) }
+
+ it 'does not process links a user can not reference' do
+ expect(subject).to receive(:nodes_user_can_reference).
+ with(user, [link]).
+ and_return([])
+
+ expect(subject).to receive(:referenced_by).with([])
+
+ subject.gather_references([link])
+ end
+
+ it 'does not process links a user can not see' do
+ expect(subject).to receive(:nodes_user_can_reference).
+ with(user, [link]).
+ and_return([link])
+
+ expect(subject).to receive(:nodes_visible_to_user).
+ with(user, [link]).
+ and_return([])
+
+ expect(subject).to receive(:referenced_by).with([])
+
+ subject.gather_references([link])
+ end
+
+ it 'returns the references if a user can reference and see a link' do
+ expect(subject).to receive(:nodes_user_can_reference).
+ with(user, [link]).
+ and_return([link])
+
+ expect(subject).to receive(:nodes_visible_to_user).
+ with(user, [link]).
+ and_return([link])
+
+ expect(subject).to receive(:referenced_by).with([link])
+
+ subject.gather_references([link])
+ end
+ end
+
+ describe '#can?' do
+ it 'delegates the permissions check to the Ability class' do
+ user = double(:user)
+
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_project, project)
+
+ subject.can?(user, :read_project, project)
+ end
+ end
+
+ describe '#find_projects_for_hash_keys' do
+ it 'returns a list of Projects' do
+ expect(subject.find_projects_for_hash_keys(project.id => project)).
+ to eq([project])
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
new file mode 100644
index 00000000000..0b76d29fce0
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -0,0 +1,113 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::CommitParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ context 'when the link has a data-project attribute' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ context 'when the link has a data-commit attribute' do
+ before do
+ link['data-commit'] = '123'
+ end
+
+ it 'returns an Array of commits' do
+ commit = double(:commit)
+
+ allow_any_instance_of(Project).to receive(:valid_repo?).
+ and_return(true)
+
+ expect(subject).to receive(:find_commits).
+ with(project, ['123']).
+ and_return([commit])
+
+ expect(subject.referenced_by([link])).to eq([commit])
+ end
+
+ it 'returns an empty Array when the commit could not be found' do
+ allow_any_instance_of(Project).to receive(:valid_repo?).
+ and_return(true)
+
+ expect(subject).to receive(:find_commits).
+ with(project, ['123']).
+ and_return([])
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+
+ it 'skips projects without valid repositories' do
+ allow_any_instance_of(Project).to receive(:valid_repo?).
+ and_return(false)
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-commit attribute' do
+ it 'returns an empty Array' do
+ allow_any_instance_of(Project).to receive(:valid_repo?).
+ and_return(true)
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns an empty Array' do
+ allow_any_instance_of(Project).to receive(:valid_repo?).
+ and_return(true)
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ describe '#commit_ids_per_project' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ it 'returns a Hash containing commit IDs per project' do
+ link['data-commit'] = '123'
+
+ hash = subject.commit_ids_per_project([link])
+
+ expect(hash).to be_an_instance_of(Hash)
+
+ expect(hash[project.id].to_a).to eq(['123'])
+ end
+
+ it 'does not add a project when the data-commit attribute is empty' do
+ hash = subject.commit_ids_per_project([link])
+
+ expect(hash).to be_empty
+ end
+ end
+
+ describe '#find_commits' do
+ it 'returns an Array of commit objects' do
+ commit = double(:commit)
+
+ expect(project).to receive(:commit).with('123').and_return(commit)
+ expect(project).to receive(:valid_repo?).and_return(true)
+
+ expect(subject.find_commits(project, %w{123})).to eq([commit])
+ end
+
+ it 'skips commit IDs for which no commit could be found' do
+ expect(project).to receive(:commit).with('123').and_return(nil)
+ expect(project).to receive(:valid_repo?).and_return(true)
+
+ expect(subject.find_commits(project, %w{123})).to eq([])
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
new file mode 100644
index 00000000000..ba982f38542
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::CommitRangeParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ context 'when the link has a data-project attribute' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ context 'when the link as a data-commit-range attribute' do
+ before do
+ link['data-commit-range'] = '123..456'
+ end
+
+ it 'returns an Array of commit ranges' do
+ range = double(:range)
+
+ expect(subject).to receive(:find_object).
+ with(project, '123..456').
+ and_return(range)
+
+ expect(subject.referenced_by([link])).to eq([range])
+ end
+
+ it 'returns an empty Array when the commit range could not be found' do
+ expect(subject).to receive(:find_object).
+ with(project, '123..456').
+ and_return(nil)
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-commit-range attribute' do
+ it 'returns an empty Array' do
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns an empty Array' do
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ describe '#commit_range_ids_per_project' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ it 'returns a Hash containing range IDs per project' do
+ link['data-commit-range'] = '123..456'
+
+ hash = subject.commit_range_ids_per_project([link])
+
+ expect(hash).to be_an_instance_of(Hash)
+
+ expect(hash[project.id].to_a).to eq(['123..456'])
+ end
+
+ it 'does not add a project when the data-commit-range attribute is empty' do
+ hash = subject.commit_range_ids_per_project([link])
+
+ expect(hash).to be_empty
+ end
+ end
+
+ describe '#find_ranges' do
+ it 'returns an Array of range objects' do
+ range = double(:commit)
+
+ expect(subject).to receive(:find_object).
+ with(project, '123..456').
+ and_return(range)
+
+ expect(subject.find_ranges(project, ['123..456'])).to eq([range])
+ end
+
+ it 'skips ranges that could not be found' do
+ expect(subject).to receive(:find_object).
+ with(project, '123..456').
+ and_return(nil)
+
+ expect(subject.find_ranges(project, ['123..456'])).to eq([])
+ end
+ end
+
+ describe '#find_object' do
+ let(:range) { double(:range) }
+
+ before do
+ expect(CommitRange).to receive(:new).and_return(range)
+ end
+
+ context 'when the range has valid commits' do
+ it 'returns the commit range' do
+ expect(range).to receive(:valid_commits?).and_return(true)
+
+ expect(subject.find_object(project, '123..456')).to eq(range)
+ end
+ end
+
+ context 'when the range does not have any valid commits' do
+ it 'returns nil' do
+ expect(range).to receive(:valid_commits?).and_return(false)
+
+ expect(subject.find_object(project, '123..456')).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
new file mode 100644
index 00000000000..a6ef8394fe7
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::ExternalIssueParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ context 'when the link has a data-project attribute' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ context 'when the link has a data-external-issue attribute' do
+ it 'returns an Array of ExternalIssue instances' do
+ link['data-external-issue'] = '123'
+
+ refs = subject.referenced_by([link])
+
+ expect(refs).to eq([ExternalIssue.new('123', project)])
+ end
+ end
+
+ context 'when the link does not have a data-external-issue attribute' do
+ it 'returns an empty Array' do
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link does not have a data-project attribute' do
+ it 'returns an empty Array' do
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ describe '#issue_ids_per_project' do
+ before do
+ link['data-project'] = project.id.to_s
+ end
+
+ it 'returns a Hash containing range IDs per project' do
+ link['data-external-issue'] = '123'
+
+ hash = subject.issue_ids_per_project([link])
+
+ expect(hash).to be_an_instance_of(Hash)
+
+ expect(hash[project.id].to_a).to eq(['123'])
+ end
+
+ it 'does not add a project when the data-external-issue attribute is empty' do
+ hash = subject.issue_ids_per_project([link])
+
+ expect(hash).to be_empty
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
new file mode 100644
index 00000000000..514c752546d
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::IssueParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#nodes_visible_to_user' do
+ context 'when the link has a data-issue attribute' do
+ before do
+ link['data-issue'] = issue.id.to_s
+ end
+
+ it 'returns the nodes when the user can read the issue' do
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_issue, issue).
+ and_return(true)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns an empty Array when the user can not read the issue' do
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_issue, issue).
+ and_return(false)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-issue attribute' do
+ it 'returns an empty Array' do
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+ end
+
+ context 'when the project uses an external issue tracker' do
+ it 'returns all nodes' do
+ link = double(:link)
+
+ expect(project).to receive(:external_issue_tracker).and_return(true)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+ end
+ end
+
+ describe '#referenced_by' do
+ context 'when the link has a data-issue attribute' do
+ context 'using an existing issue ID' do
+ before do
+ link['data-issue'] = issue.id.to_s
+ end
+
+ it 'returns an Array of issues' do
+ expect(subject.referenced_by([link])).to eq([issue])
+ end
+
+ it 'returns an empty Array when the list of nodes is empty' do
+ expect(subject.referenced_by([link])).to eq([issue])
+ expect(subject.referenced_by([])).to eq([])
+ end
+ end
+ end
+ end
+
+ describe '#issues_for_nodes' do
+ it 'returns a Hash containing the issues for a list of nodes' do
+ link['data-issue'] = issue.id.to_s
+ nodes = [link]
+
+ expect(subject.issues_for_nodes(nodes)).to eq({ issue.id => issue })
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
new file mode 100644
index 00000000000..77fda47f0e7
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::LabelParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ let(:label) { create(:label, project: project) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-label attribute' do
+ context 'using an existing label ID' do
+ it 'returns an Array of labels' do
+ link['data-label'] = label.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([label])
+ end
+ end
+
+ context 'using a non-existing label ID' do
+ it 'returns an empty Array' do
+ link['data-label'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
new file mode 100644
index 00000000000..cf89ad598ea
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::MergeRequestParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ subject { described_class.new(merge_request.target_project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-merge-request attribute' do
+ context 'using an existing merge request ID' do
+ it 'returns an Array of merge requests' do
+ link['data-merge-request'] = merge_request.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([merge_request])
+ end
+ end
+
+ context 'using a non-existing merge request ID' do
+ it 'returns an empty Array' do
+ link['data-merge-request'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
new file mode 100644
index 00000000000..6aa45a22cc4
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::MilestoneParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-milestone attribute' do
+ context 'using an existing milestone ID' do
+ it 'returns an Array of milestones' do
+ link['data-milestone'] = milestone.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([milestone])
+ end
+ end
+
+ context 'using a non-existing milestone ID' do
+ it 'returns an empty Array' do
+ link['data-milestone'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
new file mode 100644
index 00000000000..59127b7c5d1
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::SnippetParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
+ let(:snippet) { create(:snippet, project: project) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ describe 'when the link has a data-snippet attribute' do
+ context 'using an existing snippet ID' do
+ it 'returns an Array of snippets' do
+ link['data-snippet'] = snippet.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([snippet])
+ end
+ end
+
+ context 'using a non-existing snippet ID' do
+ it 'returns an empty Array' do
+ link['data-snippet'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
new file mode 100644
index 00000000000..9a82891297d
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -0,0 +1,189 @@
+require 'spec_helper'
+
+describe Banzai::ReferenceParser::UserParser, lib: true do
+ include ReferenceParserHelpers
+
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :public, group: group, creator: user) }
+ subject { described_class.new(project, user) }
+ let(:link) { empty_html_link }
+
+ describe '#referenced_by' do
+ context 'when the link has a data-group attribute' do
+ context 'using an existing group ID' do
+ before do
+ link['data-group'] = project.group.id.to_s
+ end
+
+ it 'returns the users of the group' do
+ create(:group_member, group: group, user: user)
+
+ expect(subject.referenced_by([link])).to eq([user])
+ end
+
+ it 'returns an empty Array when the group has no users' do
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+
+ context 'using a non-existing group ID' do
+ it 'returns an empty Array' do
+ link['data-group'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+
+ context 'when the link has a data-user attribute' do
+ it 'returns an Array of users' do
+ link['data-user'] = user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([user])
+ end
+ end
+
+ context 'when the link has a data-project attribute' do
+ context 'using an existing project ID' do
+ let(:contributor) { create(:user) }
+
+ before do
+ project.team << [user, :developer]
+ project.team << [contributor, :developer]
+ end
+
+ it 'returns the members of a project' do
+ link['data-project'] = project.id.to_s
+
+ # This uses an explicit sort to make sure this spec doesn't randomly
+ # fail when objects are returned in a different order.
+ refs = subject.referenced_by([link]).sort_by(&:id)
+
+ expect(refs).to eq([user, contributor])
+ end
+ end
+
+ context 'using a non-existing project ID' do
+ it 'returns an empty Array' do
+ link['data-project'] = ''
+
+ expect(subject.referenced_by([link])).to eq([])
+ end
+ end
+ end
+ end
+
+ describe '#nodes_visible_to_use?' do
+ context 'when the link has a data-group attribute' do
+ context 'using an existing group ID' do
+ before do
+ link['data-group'] = group.id.to_s
+ end
+
+ it 'returns the nodes if the user can read the group' do
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_group, group).
+ and_return(true)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns an empty Array if the user can not read the group' do
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_group, group).
+ and_return(false)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-group attribute' do
+ context 'with a data-project attribute' do
+ it 'returns the nodes if the attribute value equals the current project ID' do
+ link['data-project'] = project.id.to_s
+
+ expect(Ability.abilities).not_to receive(:allowed?)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns the nodes if the user can read the project' do
+ other_project = create(:empty_project, :public)
+
+ link['data-project'] = other_project.id.to_s
+
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_project, other_project).
+ and_return(true)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+
+ it 'returns an empty Array if the user can not read the project' do
+ other_project = create(:empty_project, :public)
+
+ link['data-project'] = other_project.id.to_s
+
+ expect(Ability.abilities).to receive(:allowed?).
+ with(user, :read_project, other_project).
+ and_return(false)
+
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([])
+ end
+ end
+
+ context 'without a data-project attribute' do
+ it 'returns the nodes' do
+ expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
+ end
+ end
+ end
+ end
+ end
+
+ describe '#nodes_user_can_reference' do
+ context 'when the link has a data-author attribute' do
+ it 'returns the nodes when the user is a member of the project' do
+ other_project = create(:project)
+ other_project.team << [user, :developer]
+
+ link['data-project'] = other_project.id.to_s
+ link['data-author'] = user.id.to_s
+
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([link])
+ end
+
+ it 'returns an empty Array when the project could not be found' do
+ link['data-project'] = ''
+ link['data-author'] = user.id.to_s
+
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([])
+ end
+
+ it 'returns an empty Array when the user could not be found' do
+ other_project = create(:project)
+
+ link['data-project'] = other_project.id.to_s
+ link['data-author'] = ''
+
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([])
+ end
+
+ it 'returns an empty Array when the user is not a team member' do
+ other_project = create(:project)
+
+ link['data-project'] = other_project.id.to_s
+ link['data-author'] = user.id.to_s
+
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([])
+ end
+ end
+
+ context 'when the link does not have a data-author attribute' do
+ it 'returns the nodes' do
+ expect(subject.nodes_user_can_reference(user, [link])).to eq([link])
+ end
+ end
+ end
+end
diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb
index 3a2b568f4c7..898f1e84ab0 100644
--- a/spec/lib/ci/ansi2html_spec.rb
+++ b/spec/lib/ci/ansi2html_spec.rb
@@ -4,131 +4,185 @@ describe Ci::Ansi2html, lib: true do
subject { Ci::Ansi2html }
it "prints non-ansi as-is" do
- expect(subject.convert("Hello")).to eq('Hello')
+ expect(subject.convert("Hello")[:html]).to eq('Hello')
end
it "strips non-color-changing controll sequences" do
- expect(subject.convert("Hello \e[2Kworld")).to eq('Hello world')
+ expect(subject.convert("Hello \e[2Kworld")[:html]).to eq('Hello world')
end
it "prints simply red" do
- expect(subject.convert("\e[31mHello\e[0m")).to eq('<span class="term-fg-red">Hello</span>')
+ expect(subject.convert("\e[31mHello\e[0m")[:html]).to eq('<span class="term-fg-red">Hello</span>')
end
it "prints simply red without trailing reset" do
- expect(subject.convert("\e[31mHello")).to eq('<span class="term-fg-red">Hello</span>')
+ expect(subject.convert("\e[31mHello")[:html]).to eq('<span class="term-fg-red">Hello</span>')
end
it "prints simply yellow" do
- expect(subject.convert("\e[33mHello\e[0m")).to eq('<span class="term-fg-yellow">Hello</span>')
+ expect(subject.convert("\e[33mHello\e[0m")[:html]).to eq('<span class="term-fg-yellow">Hello</span>')
end
it "prints default on blue" do
- expect(subject.convert("\e[39;44mHello")).to eq('<span class="term-bg-blue">Hello</span>')
+ expect(subject.convert("\e[39;44mHello")[:html]).to eq('<span class="term-bg-blue">Hello</span>')
end
it "prints red on blue" do
- expect(subject.convert("\e[31;44mHello")).to eq('<span class="term-fg-red term-bg-blue">Hello</span>')
+ expect(subject.convert("\e[31;44mHello")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello</span>')
end
it "resets colors after red on blue" do
- expect(subject.convert("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> world')
+ expect(subject.convert("\e[31;44mHello\e[0m world")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello</span> world')
end
it "performs color change from red/blue to yellow/blue" do
- expect(subject.convert("\e[31;44mHello \e[33mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-blue">world</span>')
+ expect(subject.convert("\e[31;44mHello \e[33mworld")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-blue">world</span>')
end
it "performs color change from red/blue to yellow/green" do
- expect(subject.convert("\e[31;44mHello \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-green">world</span>')
+ expect(subject.convert("\e[31;44mHello \e[33;42mworld")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-green">world</span>')
end
it "performs color change from red/blue to reset to yellow/green" do
- expect(subject.convert("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>')
+ expect(subject.convert("\e[31;44mHello\e[0m \e[33;42mworld")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>')
end
it "ignores unsupported codes" do
- expect(subject.convert("\e[51mHello\e[0m")).to eq('Hello')
+ expect(subject.convert("\e[51mHello\e[0m")[:html]).to eq('Hello')
end
it "prints light red" do
- expect(subject.convert("\e[91mHello\e[0m")).to eq('<span class="term-fg-l-red">Hello</span>')
+ expect(subject.convert("\e[91mHello\e[0m")[:html]).to eq('<span class="term-fg-l-red">Hello</span>')
end
it "prints default on light red" do
- expect(subject.convert("\e[101mHello\e[0m")).to eq('<span class="term-bg-l-red">Hello</span>')
+ expect(subject.convert("\e[101mHello\e[0m")[:html]).to eq('<span class="term-bg-l-red">Hello</span>')
end
it "performs color change from red/blue to default/blue" do
- expect(subject.convert("\e[31;44mHello \e[39mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
+ expect(subject.convert("\e[31;44mHello \e[39mworld")[:html]).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
end
it "performs color change from light red/blue to default/blue" do
- expect(subject.convert("\e[91;44mHello \e[39mworld")).to eq('<span class="term-fg-l-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
+ expect(subject.convert("\e[91;44mHello \e[39mworld")[:html]).to eq('<span class="term-fg-l-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
end
it "prints bold text" do
- expect(subject.convert("\e[1mHello")).to eq('<span class="term-bold">Hello</span>')
+ expect(subject.convert("\e[1mHello")[:html]).to eq('<span class="term-bold">Hello</span>')
end
it "resets bold text" do
- expect(subject.convert("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span> world')
- expect(subject.convert("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span> world')
+ expect(subject.convert("\e[1mHello\e[21m world")[:html]).to eq('<span class="term-bold">Hello</span> world')
+ expect(subject.convert("\e[1mHello\e[22m world")[:html]).to eq('<span class="term-bold">Hello</span> world')
end
it "prints italic text" do
- expect(subject.convert("\e[3mHello")).to eq('<span class="term-italic">Hello</span>')
+ expect(subject.convert("\e[3mHello")[:html]).to eq('<span class="term-italic">Hello</span>')
end
it "resets italic text" do
- expect(subject.convert("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span> world')
+ expect(subject.convert("\e[3mHello\e[23m world")[:html]).to eq('<span class="term-italic">Hello</span> world')
end
it "prints underlined text" do
- expect(subject.convert("\e[4mHello")).to eq('<span class="term-underline">Hello</span>')
+ expect(subject.convert("\e[4mHello")[:html]).to eq('<span class="term-underline">Hello</span>')
end
it "resets underlined text" do
- expect(subject.convert("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span> world')
+ expect(subject.convert("\e[4mHello\e[24m world")[:html]).to eq('<span class="term-underline">Hello</span> world')
end
it "prints concealed text" do
- expect(subject.convert("\e[8mHello")).to eq('<span class="term-conceal">Hello</span>')
+ expect(subject.convert("\e[8mHello")[:html]).to eq('<span class="term-conceal">Hello</span>')
end
it "resets concealed text" do
- expect(subject.convert("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span> world')
+ expect(subject.convert("\e[8mHello\e[28m world")[:html]).to eq('<span class="term-conceal">Hello</span> world')
end
it "prints crossed-out text" do
- expect(subject.convert("\e[9mHello")).to eq('<span class="term-cross">Hello</span>')
+ expect(subject.convert("\e[9mHello")[:html]).to eq('<span class="term-cross">Hello</span>')
end
it "resets crossed-out text" do
- expect(subject.convert("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span> world')
+ expect(subject.convert("\e[9mHello\e[29m world")[:html]).to eq('<span class="term-cross">Hello</span> world')
end
it "can print 256 xterm fg colors" do
- expect(subject.convert("\e[38;5;16mHello")).to eq('<span class="xterm-fg-16">Hello</span>')
+ expect(subject.convert("\e[38;5;16mHello")[:html]).to eq('<span class="xterm-fg-16">Hello</span>')
end
it "can print 256 xterm fg colors on normal magenta background" do
- expect(subject.convert("\e[38;5;16;45mHello")).to eq('<span class="xterm-fg-16 term-bg-magenta">Hello</span>')
+ expect(subject.convert("\e[38;5;16;45mHello")[:html]).to eq('<span class="xterm-fg-16 term-bg-magenta">Hello</span>')
end
it "can print 256 xterm bg colors" do
- expect(subject.convert("\e[48;5;240mHello")).to eq('<span class="xterm-bg-240">Hello</span>')
+ expect(subject.convert("\e[48;5;240mHello")[:html]).to eq('<span class="xterm-bg-240">Hello</span>')
end
it "can print 256 xterm bg colors on normal magenta foreground" do
- expect(subject.convert("\e[48;5;16;35mHello")).to eq('<span class="term-fg-magenta xterm-bg-16">Hello</span>')
+ expect(subject.convert("\e[48;5;16;35mHello")[:html]).to eq('<span class="term-fg-magenta xterm-bg-16">Hello</span>')
end
it "prints bold colored text vividly" do
- expect(subject.convert("\e[1;31mHello\e[0m")).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
+ expect(subject.convert("\e[1;31mHello\e[0m")[:html]).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
end
it "prints bold light colored text correctly" do
- expect(subject.convert("\e[1;91mHello\e[0m")).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
+ expect(subject.convert("\e[1;91mHello\e[0m")[:html]).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
+ end
+
+ it "prints &lt;" do
+ expect(subject.convert("<")[:html]).to eq('&lt;')
+ end
+
+ describe "incremental update" do
+ shared_examples 'stateable converter' do
+ let(:pass1) { subject.convert(pre_text) }
+ let(:pass2) { subject.convert(pre_text + text, pass1[:state]) }
+
+ it "to returns html to append" do
+ expect(pass2[:append]).to be_truthy
+ expect(pass2[:html]).to eq(html)
+ expect(pass1[:text] + pass2[:text]).to eq(pre_text + text)
+ expect(pass1[:html] + pass2[:html]).to eq(pre_html + html)
+ end
+ end
+
+ context "with split word" do
+ let(:pre_text) { "\e[1mHello" }
+ let(:pre_html) { "<span class=\"term-bold\">Hello</span>" }
+ let(:text) { "\e[1mWorld" }
+ let(:html) { "<span class=\"term-bold\"></span><span class=\"term-bold\">World</span>" }
+
+ it_behaves_like 'stateable converter'
+ end
+
+ context "with split sequence" do
+ let(:pre_text) { "\e[1m" }
+ let(:pre_html) { "<span class=\"term-bold\"></span>" }
+ let(:text) { "Hello" }
+ let(:html) { "<span class=\"term-bold\">Hello</span>" }
+
+ it_behaves_like 'stateable converter'
+ end
+
+ context "with partial sequence" do
+ let(:pre_text) { "Hello\e" }
+ let(:pre_html) { "Hello" }
+ let(:text) { "[1m World" }
+ let(:html) { "<span class=\"term-bold\"> World</span>" }
+
+ it_behaves_like 'stateable converter'
+ end
+
+ context 'with new line' do
+ let(:pre_text) { "Hello\r" }
+ let(:pre_html) { "Hello\r" }
+ let(:text) { "\nWorld" }
+ let(:html) { "<br>World" }
+
+ it_behaves_like 'stateable converter'
+ end
end
end
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 50a77308cde..9d1215a5760 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -12,5 +12,12 @@ describe Ci::Charts, lib: true do
chart = Ci::Charts::BuildTime.new(@commit.project)
expect(chart.build_times).to eq([2])
end
+
+ it 'should handle nil build times' do
+ create(:ci_commit, duration: nil, project: @commit.project)
+
+ chart = Ci::Charts::BuildTime.new(@commit.project)
+ expect(chart.build_times).to eq([2, 0])
+ end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index c7ab3185378..7375539cf17 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -443,12 +443,12 @@ module Ci
context 'when job variables are defined' do
context 'when syntax is correct' do
it 'returns job variables' do
- variables = {
+ variables = {
KEY1: 'value1',
SOME_KEY_2: 'value2'
}
- config = YAML.dump(
+ config = YAML.dump(
{ before_script: ['pwd'],
rspec: {
variables: variables,
@@ -619,19 +619,19 @@ module Ci
context 'no dependencies' do
let(:dependencies) { }
- it { expect { subject }.to_not raise_error }
+ it { expect { subject }.not_to raise_error }
end
context 'dependencies to builds' do
let(:dependencies) { ['build1', 'build2'] }
- it { expect { subject }.to_not raise_error }
+ it { expect { subject }.not_to raise_error }
end
context 'dependencies to builds defined as symbols' do
let(:dependencies) { [:build1, :build2] }
- it { expect { subject }.to_not raise_error }
+ it { expect { subject }.not_to raise_error }
end
context 'undefined dependency' do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
new file mode 100644
index 00000000000..4d8cb787dde
--- /dev/null
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe ContainerRegistry::Blob do
+ let(:digest) { 'sha256:0123456789012345' }
+ let(:config) do
+ {
+ 'digest' => digest,
+ 'mediaType' => 'binary',
+ 'size' => 1000
+ }
+ end
+
+ let(:registry) { ContainerRegistry::Registry.new('http://example.com') }
+ let(:repository) { registry.repository('group/test') }
+ let(:blob) { repository.blob(config) }
+
+ it { expect(blob).to respond_to(:repository) }
+ it { expect(blob).to delegate_method(:registry).to(:repository) }
+ it { expect(blob).to delegate_method(:client).to(:repository) }
+
+ context '#path' do
+ subject { blob.path }
+
+ it { is_expected.to eq('example.com/group/test@sha256:0123456789012345') }
+ end
+
+ context '#digest' do
+ subject { blob.digest }
+
+ it { is_expected.to eq(digest) }
+ end
+
+ context '#type' do
+ subject { blob.type }
+
+ it { is_expected.to eq('binary') }
+ end
+
+ context '#revision' do
+ subject { blob.revision }
+
+ it { is_expected.to eq('0123456789012345') }
+ end
+
+ context '#short_revision' do
+ subject { blob.short_revision }
+
+ it { is_expected.to eq('012345678') }
+ end
+
+ context '#delete' do
+ before do
+ stub_request(:delete, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345').
+ to_return(status: 200)
+ end
+
+ subject { blob.delete }
+
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
new file mode 100644
index 00000000000..4f3f8b24fc4
--- /dev/null
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe ContainerRegistry::Registry do
+ let(:path) { nil }
+ let(:registry) { described_class.new('http://example.com', path: path) }
+
+ subject { registry }
+
+ it { is_expected.to respond_to(:client) }
+ it { is_expected.to respond_to(:uri) }
+ it { is_expected.to respond_to(:path) }
+
+ it { expect(subject.repository('test')).not_to be_nil }
+
+ context '#path' do
+ subject { registry.path }
+
+ context 'path from URL' do
+ it { is_expected.to eq('example.com') }
+ end
+
+ context 'custom path' do
+ let(:path) { 'registry.example.com' }
+
+ it { is_expected.to eq(path) }
+ end
+ end
+end
diff --git a/spec/lib/container_registry/repository_spec.rb b/spec/lib/container_registry/repository_spec.rb
new file mode 100644
index 00000000000..279709521c9
--- /dev/null
+++ b/spec/lib/container_registry/repository_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe ContainerRegistry::Repository do
+ let(:registry) { ContainerRegistry::Registry.new('http://example.com') }
+ let(:repository) { registry.repository('group/test') }
+
+ it { expect(repository).to respond_to(:registry) }
+ it { expect(repository).to delegate_method(:client).to(:registry) }
+ it { expect(repository.tag('test')).not_to be_nil }
+
+ context '#path' do
+ subject { repository.path }
+
+ it { is_expected.to eq('example.com/group/test') }
+ end
+
+ context 'manifest processing' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/tags/list').
+ with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }).
+ to_return(
+ status: 200,
+ body: JSON.dump(tags: ['test']),
+ headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' })
+ end
+
+ context '#manifest' do
+ subject { repository.manifest }
+
+ it { is_expected.not_to be_nil }
+ end
+
+ context '#valid?' do
+ subject { repository.valid? }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context '#tags' do
+ subject { repository.tags }
+
+ it { is_expected.not_to be_empty }
+ end
+ end
+
+ context '#delete_tags' do
+ let(:tag) { ContainerRegistry::Tag.new(repository, 'tag') }
+
+ before { expect(repository).to receive(:tags).twice.and_return([tag]) }
+
+ subject { repository.delete_tags }
+
+ context 'succeeds' do
+ before { expect(tag).to receive(:delete).and_return(true) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'any fails' do
+ before { expect(tag).to receive(:delete).and_return(false) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
new file mode 100644
index 00000000000..858cb0bb134
--- /dev/null
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe ContainerRegistry::Tag do
+ let(:registry) { ContainerRegistry::Registry.new('http://example.com') }
+ let(:repository) { registry.repository('group/test') }
+ let(:tag) { repository.tag('tag') }
+ let(:headers) { { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' } }
+
+ it { expect(tag).to respond_to(:repository) }
+ it { expect(tag).to delegate_method(:registry).to(:repository) }
+ it { expect(tag).to delegate_method(:client).to(:repository) }
+
+ context '#path' do
+ subject { tag.path }
+
+ it { is_expected.to eq('example.com/group/test:tag') }
+ end
+
+ context 'manifest processing' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/manifests/tag').
+ with(headers: headers).
+ to_return(
+ status: 200,
+ body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'),
+ headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' })
+ end
+
+ context '#layers' do
+ subject { tag.layers }
+
+ it { expect(subject.length).to eq(1) }
+ end
+
+ context '#total_size' do
+ subject { tag.total_size }
+
+ it { is_expected.to eq(2319870) }
+ end
+
+ context 'config processing' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
+ with(headers: { 'Accept' => 'application/octet-stream' }).
+ to_return(
+ status: 200,
+ body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
+ end
+
+ context '#config' do
+ subject { tag.config }
+
+ it { is_expected.not_to be_nil }
+ end
+
+ context '#created_at' do
+ subject { tag.created_at }
+
+ it { is_expected.not_to be_nil }
+ end
+ end
+ end
+
+ context 'manifest digest' do
+ before do
+ stub_request(:head, 'http://example.com/v2/group/test/manifests/tag').
+ with(headers: headers).
+ to_return(status: 200, headers: { 'Docker-Content-Digest' => 'sha256:digest' })
+ end
+
+ context '#digest' do
+ subject { tag.digest }
+
+ it { is_expected.to eq('sha256:digest') }
+ end
+
+ context '#delete' do
+ before do
+ stub_request(:delete, 'http://example.com/v2/group/test/manifests/sha256:digest').
+ with(headers: headers).
+ to_return(status: 200)
+ end
+
+ subject { tag.delete }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
index 53f5d6c5c80..88a71528867 100644
--- a/spec/lib/gitlab/akismet_helper_spec.rb
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -6,8 +6,8 @@ describe Gitlab::AkismetHelper, type: :helper do
before do
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
- current_application_settings.akismet_enabled = true
- current_application_settings.akismet_api_key = '12345'
+ allow_any_instance_of(ApplicationSetting).to receive(:akismet_enabled).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:akismet_api_key).and_return('12345')
end
describe '#check_for_spam?' do
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index af839f42421..7718689e6d4 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -59,7 +59,7 @@ describe Gitlab::BitbucketImport::Client, lib: true do
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
+ expect { described_class.from_project(project) }.not_to raise_error
end
end
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index acca0b08bab..711a3e1c7d4 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -10,8 +10,8 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
'path/dir_1/subdir/subfile' => { size: 10 },
'path/second_dir' => {},
'path/second_dir/dir_3/file_2' => { size: 10 },
- 'path/second_dir/dir_3/file_3'=> { size: 10 },
- 'another_directory/'=> {},
+ 'path/second_dir/dir_3/file_3' => { size: 10 },
+ 'another_directory/' => {},
'another_file' => {},
'/file/with/absolute_path' => {} }
end
@@ -122,7 +122,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'empty path', path: '' do
subject { |example| path(example) }
- it { is_expected.to_not have_parent }
+ it { is_expected.not_to have_parent }
describe '#children' do
subject { |example| path(example).children }
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
new file mode 100644
index 00000000000..35ade7a2be0
--- /dev/null
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+describe Gitlab::Database::MigrationHelpers, lib: true do
+ let(:model) do
+ ActiveRecord::Migration.new.extend(
+ Gitlab::Database::MigrationHelpers
+ )
+ end
+
+ before { allow(model).to receive(:puts) }
+
+ describe '#add_concurrent_index' do
+ context 'outside a transaction' do
+ before do
+ expect(model).to receive(:transaction_open?).and_return(false)
+ end
+
+ context 'using PostgreSQL' do
+ it 'creates the index concurrently' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
+ expect(model).to receive(:add_index).
+ with(:users, :foo, algorithm: :concurrently)
+
+ model.add_concurrent_index(:users, :foo)
+ end
+ end
+
+ context 'using MySQL' do
+ it 'creates a regular index' do
+ expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
+ expect(model).to receive(:add_index).
+ with(:users, :foo)
+
+ model.add_concurrent_index(:users, :foo)
+ end
+ end
+ end
+
+ context 'inside a transaction' do
+ it 'raises RuntimeError' do
+ expect(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.add_concurrent_index(:users, :foo) }.
+ to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ describe '#update_column_in_batches' do
+ before do
+ create_list(:empty_project, 5)
+ end
+
+ it 'updates all the rows in a table' do
+ model.update_column_in_batches(:projects, :import_error, 'foo')
+
+ expect(Project.where(import_error: 'foo').count).to eq(5)
+ end
+
+ it 'updates boolean values correctly' do
+ model.update_column_in_batches(:projects, :archived, true)
+
+ expect(Project.where(archived: true).count).to eq(5)
+ end
+ end
+
+ describe '#add_column_with_default' do
+ context 'outside of a transaction' do
+ before do
+ expect(model).to receive(:transaction_open?).and_return(false)
+
+ expect(model).to receive(:transaction).twice.and_yield
+
+ expect(model).to receive(:add_column).
+ with(:projects, :foo, :integer, default: nil)
+
+ expect(model).to receive(:change_column_default).
+ with(:projects, :foo, 10)
+ end
+
+ it 'adds the column while allowing NULL values' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10)
+
+ expect(model).not_to receive(:change_column_null)
+
+ model.add_column_with_default(:projects, :foo, :integer,
+ default: 10,
+ allow_null: true)
+ end
+
+ it 'adds the column while not allowing NULL values' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10)
+
+ expect(model).to receive(:change_column_null).
+ with(:projects, :foo, false)
+
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end
+
+ it 'removes the added column whenever updating the rows fails' do
+ expect(model).to receive(:update_column_in_batches).
+ with(:projects, :foo, 10).
+ and_raise(RuntimeError)
+
+ expect(model).to receive(:remove_column).
+ with(:projects, :foo)
+
+ expect do
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end.to raise_error(RuntimeError)
+ end
+ end
+
+ context 'inside a transaction' do
+ it 'raises RuntimeError' do
+ expect(model).to receive(:transaction_open?).and_return(true)
+
+ expect do
+ model.add_column_with_default(:projects, :foo, :integer, default: 10)
+ end.to raise_error(RuntimeError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index b2d7a799810..c19f33e2224 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Email::Message::RepositoryPush do
let!(:author) { create(:author, name: 'Author') }
let(:message) do
- described_class.new(Notify, project.id, 'recipient@example.com', opts)
+ described_class.new(Notify, project.id, opts)
end
context 'new commits have been pushed to repository' do
@@ -57,7 +57,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#diffs' do
subject { message.diffs }
- it { is_expected.to all(be_an_instance_of Gitlab::Git::Diff) }
+ it { is_expected.to all(be_an_instance_of Gitlab::Diff::File) }
end
describe '#diffs_count' do
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 0a7ca3ec848..0af249d8690 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -33,8 +33,8 @@ describe Gitlab::Gfm::ReferenceRewriter do
end
it { is_expected.to include issue_first.to_reference(new_project) }
- it { is_expected.to_not include issue_second.to_reference(new_project) }
- it { is_expected.to_not include merge_request.to_reference(new_project) }
+ it { is_expected.not_to include issue_second.to_reference(new_project) }
+ it { is_expected.not_to include merge_request.to_reference(new_project) }
end
context 'description ambigous elements' do
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index eda956e6f0a..6eca33f9fee 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -32,13 +32,13 @@ describe Gitlab::Gfm::UploadsRewriter do
let(:new_paths) { new_files.map(&:path) }
it 'rewrites content' do
- expect(new_text).to_not eq text
+ expect(new_text).not_to eq text
expect(new_text.length).to eq text.length
end
it 'copies files' do
expect(new_files).to all(exist)
- expect(old_paths).to_not match_array new_paths
+ expect(old_paths).not_to match_array new_paths
expect(old_paths).to all(include(old_project.path_with_namespace))
expect(new_paths).to all(include(new_project.path_with_namespace))
end
@@ -48,8 +48,8 @@ describe Gitlab::Gfm::UploadsRewriter do
end
it 'generates a new secret for each file' do
- expect(new_paths).to_not include image_uploader.secret
- expect(new_paths).to_not include zip_uploader.secret
+ expect(new_paths).not_to include image_uploader.secret
+ expect(new_paths).not_to include zip_uploader.secret
end
end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
new file mode 100644
index 00000000000..3cb634ba010
--- /dev/null
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::BranchFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:repo) { double }
+ let(:raw) do
+ {
+ ref: 'feature',
+ repo: repo,
+ sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+ }
+ end
+
+ describe '#exists?' do
+ it 'returns true when branch exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.exists?).to eq true
+ end
+
+ it 'returns false when branch does not exist' do
+ branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
+
+ expect(branch.exists?).to eq false
+ end
+ end
+
+ describe '#name' do
+ it 'returns raw ref when branch exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.name).to eq 'feature'
+ end
+
+ it 'returns formatted ref when branch does not exist' do
+ branch = described_class.new(project, double(raw.merge(ref: 'removed-branch')))
+
+ expect(branch.name).to eq 'removed-branch-2e5d3239'
+ end
+ end
+
+ describe '#repo' do
+ it 'returns raw repo' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.repo).to eq repo
+ end
+ end
+
+ describe '#sha' do
+ it 'returns raw sha' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+ end
+ end
+
+ describe '#valid?' do
+ it 'returns true when repository exists' do
+ branch = described_class.new(project, double(raw))
+
+ expect(branch.valid?).to eq true
+ end
+
+ it 'returns false when repository does not exist' do
+ branch = described_class.new(project, double(raw.merge(repo: nil)))
+
+ expect(branch.valid?).to eq false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index e59c0ca110e..120f59e6e71 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -4,9 +4,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) }
let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository }
- let(:source_branch) { double(ref: 'feature', repo: source_repo) }
+ let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:target_repo) { repository }
- let(:target_branch) { double(ref: 'master', repo: target_repo) }
+ let(:target_branch) { double(ref: 'master', repo: target_repo, sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7') }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -41,8 +41,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'opened',
milestone: nil,
author_id: project.creator_id,
@@ -66,8 +68,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'closed',
milestone: nil,
author_id: project.creator_id,
@@ -91,8 +95,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
source_branch: 'feature',
+ head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b',
target_project: project,
target_branch: 'master',
+ base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7',
state: 'merged',
milestone: nil,
author_id: project.creator_id,
@@ -137,11 +143,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:milestone) { double(number: 45) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
- it 'returns nil when milestone does not exists' do
+ it 'returns nil when milestone does not exist' do
expect(pull_request.attributes.fetch(:milestone)).to be_nil
end
- it 'returns milestone when is exists' do
+ it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone
@@ -158,36 +164,16 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
describe '#valid?' do
- let(:invalid_branch) { double(ref: 'invalid-branch').as_null_object }
-
- context 'when source, and target repositories are the same' do
- context 'and source and target branches exists' do
- let(:raw_data) { double(base_data.merge(head: source_branch, base: target_branch)) }
-
- it 'returns true' do
- expect(pull_request.valid?).to eq true
- end
- end
-
- context 'and source branch doesn not exists' do
- let(:raw_data) { double(base_data.merge(head: invalid_branch, base: target_branch)) }
-
- it 'returns false' do
- expect(pull_request.valid?).to eq false
- end
- end
-
- context 'and target branch doesn not exists' do
- let(:raw_data) { double(base_data.merge(head: source_branch, base: invalid_branch)) }
+ context 'when source, and target repos are not a fork' do
+ let(:raw_data) { double(base_data) }
- it 'returns false' do
- expect(pull_request.valid?).to eq false
- end
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
end
end
context 'when source repo is a fork' do
- let(:source_repo) { double(id: 2, fork: true) }
+ let(:source_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
it 'returns false' do
@@ -196,7 +182,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when target repo is a fork' do
- let(:target_repo) { double(id: 2, fork: true) }
+ let(:target_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
it 'returns false' do
diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/gitignore_spec.rb
new file mode 100644
index 00000000000..72baa516cc4
--- /dev/null
+++ b/spec/lib/gitlab/gitignore_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::Gitignore do
+ subject { Gitlab::Gitignore }
+
+ describe '.all' do
+ it 'strips the gitignore suffix' do
+ expect(subject.all.first.name).not_to end_with('.gitignore')
+ end
+
+ it 'combines the globals and rest' do
+ all = subject.all.map(&:name)
+
+ expect(all).to include('Vim')
+ expect(all).to include('Ruby')
+ end
+ end
+
+ describe '.find' do
+ it 'returns nil if the file does not exist' do
+ expect(subject.find('mepmep-yadida')).to be nil
+ end
+
+ it 'returns the Gitignore object of a valid file' do
+ ruby = subject.find('Ruby')
+
+ expect(ruby).to be_a Gitlab::Gitignore
+ expect(ruby.name).to eq('Ruby')
+ end
+ end
+
+ describe '#content' do
+ it 'loads the full file' do
+ gitignore = subject.new(Rails.root.join('vendor/gitignore/Ruby.gitignore'))
+
+ expect(gitignore.name).to eq 'Ruby'
+ expect(gitignore.content).to start_with('*.gem')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_url_spec.rb b/spec/lib/gitlab/import_url_spec.rb
deleted file mode 100644
index f758cb8693c..00000000000
--- a/spec/lib/gitlab/import_url_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportUrl do
-
- let(:credentials) { { user: 'blah', password: 'password' } }
- let(:import_url) do
- Gitlab::ImportUrl.new("https://github.com/me/project.git", credentials: credentials)
- end
-
- describe :full_url do
- it { expect(import_url.full_url).to eq("https://blah:password@github.com/me/project.git") }
- end
-
- describe :sanitized_url do
- it { expect(import_url.sanitized_url).to eq("https://github.com/me/project.git") }
- end
-
- describe :credentials do
- it { expect(import_url.credentials).to eq(credentials) }
- end
-end
diff --git a/spec/lib/gitlab/lazy_spec.rb b/spec/lib/gitlab/lazy_spec.rb
new file mode 100644
index 00000000000..b5ca89dd242
--- /dev/null
+++ b/spec/lib/gitlab/lazy_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Lazy, lib: true do
+ let(:dummy) { double(:dummy) }
+
+ context 'when not calling any methods' do
+ it 'does not call the supplied block' do
+ expect(dummy).not_to receive(:foo)
+
+ described_class.new { dummy.foo }
+ end
+ end
+
+ context 'when calling a method on the object' do
+ it 'lazy loads the value returned by the block' do
+ expect(dummy).to receive(:foo).and_return('foo')
+
+ lazy = described_class.new { dummy.foo }
+
+ expect(lazy.to_s).to eq('foo')
+ end
+ end
+
+ describe '#respond_to?' do
+ it 'returns true for a method defined on the wrapped object' do
+ lazy = described_class.new { 'foo' }
+
+ expect(lazy).to respond_to(:downcase)
+ end
+
+ it 'returns false for a method not defined on the wrapped object' do
+ lazy = described_class.new { 'foo' }
+
+ expect(lazy).not_to respond_to(:quack)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index 5852b31ab3a..88814bc474d 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -26,8 +26,8 @@ describe Gitlab::Lfs::Router, lib: true do
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
- let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
- let(:respond_with_disabled) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
+ let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
+ let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
describe 'when lfs is disabled' do
before do
@@ -368,7 +368,7 @@ describe Gitlab::Lfs::Router, lib: true do
expect(response['objects']).to be_kind_of(Array)
expect(response['objects'].first['oid']).to eq(sample_oid)
expect(response['objects'].first['size']).to eq(sample_size)
- expect(lfs_object.projects.pluck(:id)).to_not include(project.id)
+ expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
@@ -430,7 +430,7 @@ describe Gitlab::Lfs::Router, lib: true do
expect(response_body['objects'].last['oid']).to eq(sample_oid)
expect(response_body['objects'].last['size']).to eq(sample_size)
- expect(response_body['objects'].last).to_not have_key('actions')
+ expect(response_body['objects'].last).not_to have_key('actions')
end
end
end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 5c885a7a982..220e86924a2 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -56,9 +56,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:increment).
- with(:method_duration, a_kind_of(Numeric))
-
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo')
@@ -70,7 +67,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(Gitlab::Metrics).to receive(:method_call_threshold).
and_return(100)
- expect(transaction).to_not receive(:add_metric)
+ expect(transaction).not_to receive(:add_metric)
@dummy.foo
end
@@ -139,9 +136,6 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:increment).
- with(:method_duration, a_kind_of(Numeric))
-
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar')
@@ -153,7 +147,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(Gitlab::Metrics).to receive(:method_call_threshold).
and_return(100)
- expect(transaction).to_not receive(:add_metric)
+ expect(transaction).not_to receive(:add_metric)
@dummy.new.bar
end
@@ -226,7 +220,7 @@ describe Gitlab::Metrics::Instrumentation do
described_class.instrument_methods(@dummy)
- expect(@dummy).to_not respond_to(:_original_kittens)
+ expect(@dummy).not_to respond_to(:_original_kittens)
end
it 'can take a block to determine if a method should be instrumented' do
@@ -234,7 +228,7 @@ describe Gitlab::Metrics::Instrumentation do
false
end
- expect(@dummy).to_not respond_to(:_original_foo)
+ expect(@dummy).not_to respond_to(:_original_foo)
end
end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb
index 38da77adc9f..59db127674a 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/sampler_spec.rb
@@ -130,7 +130,7 @@ describe Gitlab::Metrics::Sampler do
100.times do
interval = sampler.sleep_interval
- expect(interval).to_not eq(last)
+ expect(interval).not_to eq(last)
last = interval
end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index e3293a01207..49699ffe28f 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
describe 'without a current transaction' do
it 'simply returns' do
expect_any_instance_of(Gitlab::Metrics::Transaction).
- to_not receive(:increment)
+ not_to receive(:increment)
subscriber.sql(event)
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index e01b0b4bd21..d824dc54438 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_read' do
it 'increments the cache_read duration' do
expect(subscriber).to receive(:increment).
- with(:cache_read_duration, event.duration)
+ with(:cache_read, event.duration)
subscriber.cache_read(event)
end
@@ -18,7 +18,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_write' do
it 'increments the cache_write duration' do
expect(subscriber).to receive(:increment).
- with(:cache_write_duration, event.duration)
+ with(:cache_write, event.duration)
subscriber.cache_write(event)
end
@@ -27,7 +27,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_delete' do
it 'increments the cache_delete duration' do
expect(subscriber).to receive(:increment).
- with(:cache_delete_duration, event.duration)
+ with(:cache_delete, event.duration)
subscriber.cache_delete(event)
end
@@ -36,7 +36,7 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
describe '#cache_exist?' do
it 'increments the cache_exists duration' do
expect(subscriber).to receive(:increment).
- with(:cache_exists_duration, event.duration)
+ with(:cache_exists, event.duration)
subscriber.cache_exist?(event)
end
@@ -62,9 +62,15 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
with(:cache_duration, event.duration)
expect(transaction).to receive(:increment).
+ with(:cache_count, 1)
+
+ expect(transaction).to receive(:increment).
with(:cache_delete_duration, event.duration)
- subscriber.increment(:cache_delete_duration, event.duration)
+ expect(transaction).to receive(:increment).
+ with(:cache_delete_count, 1)
+
+ subscriber.increment(:cache_delete, event.duration)
end
end
end
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
new file mode 100644
index 00000000000..fd6f684db0c
--- /dev/null
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::RailsQueueDuration do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+ let(:env) { {} }
+ let(:transaction) { double(:transaction) }
+
+ before { expect(app).to receive(:call).with(env).and_return('yay') }
+
+ describe '#call' do
+ it 'calls the app when metrics are disabled' do
+ expect(Gitlab::Metrics).to receive(:current_transaction).and_return(nil)
+ expect(middleware.call(env)).to eq('yay')
+ end
+
+ context 'when metrics are enabled' do
+ before { allow(Gitlab::Metrics).to receive(:current_transaction).and_return(transaction) }
+
+ it 'calls the app when metrics are enabled but no timing header is found' do
+ expect(middleware.call(env)).to eq('yay')
+ end
+
+ it 'sets proxy_flight_time and calls the app when the header is present' do
+ env['HTTP_GITLAB_WORHORSE_PROXY_START'] = '123'
+ expect(transaction).to receive(:set).with(:rails_queue_duration, an_instance_of(Float))
+ expect(middleware.call(env)).to eq('yay')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index f093d0a0d8b..e848d88182f 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -9,7 +9,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
before(:each) do
expect(data).to have_key(:object_attributes)
expect(data[:object_attributes]).to have_key(:url)
- expect(data[:object_attributes][:url]).to eq(Gitlab::UrlBuilder.build(note))
+ expect(data[:object_attributes][:url])
+ .to eq(Gitlab::UrlBuilder.build(note))
expect(data[:object_kind]).to eq('note')
expect(data[:user]).to eq(user.hook_attrs)
end
@@ -37,13 +38,21 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on issue' do
- let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_issue, noteable_id: issue.id, project: project) }
+ let(:issue) do
+ create(:issue, created_at: fixed_time, updated_at: fixed_time,
+ project: project)
+ end
+
+ let(:note) do
+ create(:note_on_issue, noteable: issue, project: project)
+ end
it 'returns the note and issue-specific data' do
expect(data).to have_key(:issue)
- expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
- expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
+ expect(data[:issue].except('updated_at'))
+ .to eq(issue.reload.hook_attrs.except('updated_at'))
+ expect(data[:issue]['updated_at'])
+ .to be > issue.hook_attrs['updated_at']
end
include_examples 'project hook data'
@@ -51,13 +60,23 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on merge request' do
- let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id, project: project) }
+ let(:merge_request) do
+ create(:merge_request, created_at: fixed_time,
+ updated_at: fixed_time,
+ source_project: project)
+ end
+
+ let(:note) do
+ create(:note_on_merge_request, noteable: merge_request,
+ project: project)
+ end
it 'returns the note and merge request data' do
expect(data).to have_key(:merge_request)
- expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
- expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
+ expect(data[:merge_request].except('updated_at'))
+ .to eq(merge_request.reload.hook_attrs.except('updated_at'))
+ expect(data[:merge_request]['updated_at'])
+ .to be > merge_request.hook_attrs['updated_at']
end
include_examples 'project hook data'
@@ -65,13 +84,22 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on merge request diff' do
- let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id, project: project) }
+ let(:merge_request) do
+ create(:merge_request, created_at: fixed_time, updated_at: fixed_time,
+ source_project: project)
+ end
+
+ let(:note) do
+ create(:note_on_merge_request_diff, noteable: merge_request,
+ project: project)
+ end
it 'returns the note and merge request diff data' do
expect(data).to have_key(:merge_request)
- expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
- expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
+ expect(data[:merge_request].except('updated_at'))
+ .to eq(merge_request.reload.hook_attrs.except('updated_at'))
+ expect(data[:merge_request]['updated_at'])
+ .to be > merge_request.hook_attrs['updated_at']
end
include_examples 'project hook data'
@@ -79,13 +107,22 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on project snippet' do
- let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) }
- let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id, project: project) }
+ let!(:snippet) do
+ create(:project_snippet, created_at: fixed_time, updated_at: fixed_time,
+ project: project)
+ end
+
+ let!(:note) do
+ create(:note_on_project_snippet, noteable: snippet,
+ project: project)
+ end
it 'returns the note and project snippet data' do
expect(data).to have_key(:snippet)
- expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
- expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
+ expect(data[:snippet].except('updated_at'))
+ .to eq(snippet.reload.hook_attrs.except('updated_at'))
+ expect(data[:snippet]['updated_at'])
+ .to be > snippet.hook_attrs['updated_at']
end
include_examples 'project hook data'
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index de6bb86c5dd..2ae79b50e77 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -11,13 +11,13 @@ describe Gitlab::Sherlock::Collection, lib: true do
it 'adds a new transaction' do
collection.add(transaction)
- expect(collection).to_not be_empty
+ expect(collection).not_to be_empty
end
it 'is aliased as <<' do
collection << transaction
- expect(collection).to_not be_empty
+ expect(collection).not_to be_empty
end
end
@@ -47,7 +47,7 @@ describe Gitlab::Sherlock::Collection, lib: true do
it 'returns false for a collection with a transaction' do
collection.add(transaction)
- expect(collection).to_not be_empty
+ expect(collection).not_to be_empty
end
end
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 05da915ccfd..0a620428138 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -85,7 +85,7 @@ FROM users;
frames = query.application_backtrace
expect(frames).to be_an_instance_of(Array)
- expect(frames).to_not be_empty
+ expect(frames).not_to be_empty
frames.each do |frame|
expect(frame.path).to start_with(Rails.root.to_s)
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 7553f2a045f..9fe18f253f0 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -203,7 +203,7 @@ describe Gitlab::Sherlock::Transaction, lib: true do
end
it 'only tracks queries triggered from the transaction thread' do
- expect(transaction).to_not receive(:track_query)
+ expect(transaction).not_to receive(:track_query)
Thread.new { subscription.publish('test', time, time, nil, query_data) }.
join
@@ -226,7 +226,7 @@ describe Gitlab::Sherlock::Transaction, lib: true do
end
it 'only tracks views rendered from the transaction thread' do
- expect(transaction).to_not receive(:track_view)
+ expect(transaction).not_to receive(:track_view)
Thread.new { subscription.publish('test', time, time, nil, view_data) }.
join
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
new file mode 100644
index 00000000000..de55334118f
--- /dev/null
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe Gitlab::UrlSanitizer, lib: true do
+ let(:credentials) { { user: 'blah', password: 'password' } }
+ let(:url_sanitizer) do
+ described_class.new("https://github.com/me/project.git", credentials: credentials)
+ end
+
+ describe '.sanitize' do
+ def sanitize_url(url)
+ # We want to try with multi-line content because is how error messages are formatted
+ described_class.sanitize(%Q{
+ remote: Not Found
+ fatal: repository '#{url}' not found
+ })
+ end
+
+ it 'mask the credentials from HTTP URLs' do
+ filtered_content = sanitize_url('http://user:pass@test.com/root/repoC.git/')
+
+ expect(filtered_content).to include("http://*****:*****@test.com/root/repoC.git/")
+ end
+
+ it 'mask the credentials from HTTPS URLs' do
+ filtered_content = sanitize_url('https://user:pass@test.com/root/repoA.git/')
+
+ expect(filtered_content).to include("https://*****:*****@test.com/root/repoA.git/")
+ end
+
+ it 'mask credentials from SSH URLs' do
+ filtered_content = sanitize_url('ssh://user@host.test/path/to/repo.git')
+
+ expect(filtered_content).to include("ssh://*****@host.test/path/to/repo.git")
+ end
+
+ it 'does not modify Git URLs' do
+ # git protocol does not support authentication
+ filtered_content = sanitize_url('git://host.test/path/to/repo.git')
+
+ expect(filtered_content).to include("git://host.test/path/to/repo.git")
+ end
+
+ it 'does not modify scp-like URLs' do
+ filtered_content = sanitize_url('user@server:project.git')
+
+ expect(filtered_content).to include("user@server:project.git")
+ end
+ end
+
+ describe '#sanitized_url' do
+ it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") }
+ end
+
+ describe '#credentials' do
+ it { expect(url_sanitizer.credentials).to eq(credentials) }
+ end
+
+ describe '#full_url' do
+ it { expect(url_sanitizer.full_url).to eq("https://blah:password@github.com/me/project.git") }
+
+ it 'supports scp-like URLs' do
+ sanitizer = described_class.new('user@server:project.git')
+
+ expect(sanitizer.full_url).to eq('user@server:project.git')
+ end
+ end
+
+end
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
new file mode 100644
index 00000000000..18726754517
--- /dev/null
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -0,0 +1,43 @@
+describe JSONWebToken::RSAToken do
+ let(:rsa_key) do
+ OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak
+ OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ
+ iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw
+ 2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu
+ H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al
+ A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l
+ 0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4
+ -----END RSA PRIVATE KEY-----
+ eos
+ end
+ let(:rsa_token) { described_class.new(nil) }
+ let(:rsa_encoded) { rsa_token.encoded }
+
+ before { allow_any_instance_of(described_class).to receive(:key).and_return(rsa_key) }
+
+ context 'token' do
+ context 'for valid key to be validated' do
+ before { rsa_token['key'] = 'value' }
+
+ subject { JWT.decode(rsa_encoded, rsa_key) }
+
+ it { expect{subject}.not_to raise_error }
+ it { expect(subject.first).to include('key' => 'value') }
+ it do
+ expect(subject.second).to eq(
+ "typ" => "JWT",
+ "alg" => "RS256",
+ "kid" => "OGXY:4TR7:FAVO:WEM2:XXEW:E4FP:TKL7:7ACK:TZAF:D54P:SUIA:P3B2")
+ end
+ end
+
+ context 'for invalid key to raise an exception' do
+ let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
+ subject { JWT.decode(rsa_encoded, new_key) }
+
+ it { expect{subject}.to raise_error(JWT::DecodeError) }
+ end
+ end
+end
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
new file mode 100644
index 00000000000..3d955e4d774
--- /dev/null
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -0,0 +1,18 @@
+describe JSONWebToken::Token do
+ let(:token) { described_class.new }
+
+ context 'custom parameters' do
+ let(:value) { 'value' }
+ before { token[:key] = value }
+
+ it { expect(token[:key]).to eq(value) }
+ it { expect(token.payload).to include(key: value) }
+ end
+
+ context 'embeds default payload' do
+ subject { token.payload }
+ let(:default) { token.send(:default_payload) }
+
+ it { is_expected.to include(default) }
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 495c5cbac00..818825b1477 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -51,7 +51,7 @@ describe Notify do
context 'when enabled email_author_in_body' do
before do
- allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
end
it 'contains a link to note author' do
@@ -230,7 +230,7 @@ describe Notify do
context 'when enabled email_author_in_body' do
before do
- allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
end
it 'contains a link to note author' do
@@ -454,7 +454,7 @@ describe Notify do
context 'when enabled email_author_in_body' do
before do
- allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true)
end
it 'contains a link to note author' do
@@ -593,7 +593,7 @@ describe Notify do
let(:user) { create(:user) }
let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -606,10 +606,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /Pushed new branch master/
end
@@ -624,7 +620,7 @@ describe Notify do
let(:user) { create(:user) }
let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -637,10 +633,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /Pushed new tag v1\.0/
end
@@ -654,7 +646,7 @@ describe Notify do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -667,10 +659,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /Deleted branch master/
end
@@ -680,7 +668,7 @@ describe Notify do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -693,10 +681,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /Deleted tag v1\.0/
end
@@ -709,8 +693,9 @@ describe Notify do
let(:commits) { Commit.decorate(compare.commits, nil) }
let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
let(:send_from_committer_email) { false }
+ let(:diff_refs) { [project.merge_base_commit(sample_image_commit.id, sample_commit.id), project.commit(sample_commit.id)] }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -723,10 +708,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
end
@@ -735,15 +716,15 @@ describe Notify do
is_expected.to have_body_text /Change some files/
end
- it 'includes diffs' do
- is_expected.to have_body_text /def archive_formats_regex/
+ it 'includes diffs with character-level highlighting' do
+ is_expected.to have_body_text /def<\/span> <span class=\"nf\">archive_formats_regex/
end
it 'contains a link to the diff' do
is_expected.to have_body_text /#{diff_path}/
end
- it 'doesn not contain the misleading footer' do
+ it 'does not contain the misleading footer' do
is_expected.not_to have_body_text /you are a member of/
end
@@ -817,8 +798,9 @@ describe Notify do
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits, nil) }
let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
+ let(:diff_refs) { [project.merge_base_commit(sample_commit.parent_id, sample_commit.id), project.commit(sample_commit.id)] }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
+ subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -831,10 +813,6 @@ describe Notify do
expect(sender.address).to eq(gitlab_sender)
end
- it 'is sent to recipient' do
- is_expected.to deliver_to 'devs@company.name'
- end
-
it 'has the correct subject' do
is_expected.to have_subject /#{commits.first.title}/
end
@@ -843,8 +821,8 @@ describe Notify do
is_expected.to have_body_text /Change some files/
end
- it 'includes diffs' do
- is_expected.to have_body_text /def archive_formats_regex/
+ it 'includes diffs with character-level highlighting' do
+ is_expected.to have_body_text /def<\/span> <span class=\"nf\">archive_formats_regex/
end
it 'contains a link to the diff' do
diff --git a/spec/mailers/previews/devise_mailer_preview.rb b/spec/mailers/previews/devise_mailer_preview.rb
new file mode 100644
index 00000000000..dc3062a4332
--- /dev/null
+++ b/spec/mailers/previews/devise_mailer_preview.rb
@@ -0,0 +1,11 @@
+class DeviseMailerPreview < ActionMailer::Preview
+ def confirmation_instructions_for_signup
+ user = User.new(name: 'Jane Doe', email: 'signup@example.com')
+ DeviseMailer.confirmation_instructions(user, 'faketoken', {})
+ end
+
+ def confirmation_instructions_for_new_email
+ user = User.last
+ DeviseMailer.confirmation_instructions(user, 'faketoken', {})
+ end
+end
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
index 5a85cb501dd..93de5850ba2 100644
--- a/spec/mailers/shared/notify.rb
+++ b/spec/mailers/shared/notify.rb
@@ -146,8 +146,8 @@ shared_examples 'it should have Gmail Actions links' do
end
shared_examples 'it should not have Gmail Actions links' do
- it { is_expected.to_not have_body_text '<script type="application/ld+json">' }
- it { is_expected.to_not have_body_text /ViewAction/ }
+ it { is_expected.not_to have_body_text '<script type="application/ld+json">' }
+ it { is_expected.not_to have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
new file mode 100644
index 00000000000..1acb5846fcf
--- /dev/null
+++ b/spec/models/ability_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Ability, lib: true do
+ describe '.users_that_can_read_project' do
+ context 'using a public project' do
+ it 'returns all the users' do
+ project = create(:project, :public)
+ user = build(:user)
+
+ expect(described_class.users_that_can_read_project([user], project)).
+ to eq([user])
+ end
+ end
+
+ context 'using an internal project' do
+ let(:project) { create(:project, :internal) }
+
+ it 'returns users that are administrators' do
+ user = build(:user, admin: true)
+
+ expect(described_class.users_that_can_read_project([user], project)).
+ to eq([user])
+ end
+
+ it 'returns internal users while skipping external users' do
+ user1 = build(:user)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([user1])
+ end
+
+ it 'returns external users if they are the project owner' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(project).to receive(:owner).twice.and_return(user1)
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([user1])
+ end
+
+ it 'returns external users if they are project members' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(project.team).to receive(:members).twice.and_return([user1])
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([user1])
+ end
+
+ it 'returns an empty Array if all users are external users without access' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([])
+ end
+ end
+
+ context 'using a private project' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns users that are administrators' do
+ user = build(:user, admin: true)
+
+ expect(described_class.users_that_can_read_project([user], project)).
+ to eq([user])
+ end
+
+ it 'returns external users if they are the project owner' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(project).to receive(:owner).twice.and_return(user1)
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([user1])
+ end
+
+ it 'returns external users if they are project members' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(project.team).to receive(:members).twice.and_return([user1])
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([user1])
+ end
+
+ it 'returns an empty Array if all users are internal users without access' do
+ user1 = build(:user)
+ user2 = build(:user)
+ users = [user1, user2]
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([])
+ end
+
+ it 'returns an empty Array if all users are external users without access' do
+ user1 = build(:user, external: true)
+ user2 = build(:user, external: true)
+ users = [user1, user2]
+
+ expect(described_class.users_that_can_read_project(users, project)).
+ to eq([])
+ end
+ end
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index ac12ab6c757..305f8bc88cc 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 520cf1b75de..d84f3e998f5 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1,49 +1,3 @@
-# == Schema Information
-#
-# Table name: application_settings
-#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# 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
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string
-# require_two_factor_authentication :boolean default(FALSE)
-# 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)
-# recaptcha_enabled :boolean default(FALSE)
-# recaptcha_site_key :string
-# recaptcha_private_key :string
-# metrics_port :integer default(8089)
-# sentry_enabled :boolean default(FALSE)
-# sentry_dsn :string
-#
-
require 'spec_helper'
describe ApplicationSetting, models: true do
@@ -66,6 +20,15 @@ describe ApplicationSetting, models: true do
it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
+ describe 'disabled_oauth_sign_in_sources validations' do
+ before do
+ allow(Devise).to receive(:omniauth_providers).and_return([:github])
+ end
+
+ it { is_expected.to allow_value(['github']).for(:disabled_oauth_sign_in_sources) }
+ it { is_expected.not_to allow_value(['test']).for(:disabled_oauth_sign_in_sources) }
+ end
+
it { is_expected.to validate_presence_of(:max_attachment_size) }
it do
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index f6f84db57e6..6ad8bfef4f2 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string(255)
-# font :string(255)
-#
-
require 'spec_helper'
describe BroadcastMessage, models: true do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index b5d356aa066..5c6c30c20ea 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -1,18 +1,17 @@
require 'spec_helper'
describe Ci::Build, models: true do
- let(:project) { FactoryGirl.create :project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
- let(:build) { FactoryGirl.create :ci_build, commit: commit }
+ let(:project) { create(:project) }
+ let(:commit) { create(:ci_commit, project: project) }
+ let(:build) { create(:ci_build, commit: commit) }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html }
describe '#first_pending' do
- let(:first) { FactoryGirl.create :ci_build, commit: commit, status: 'pending', created_at: Date.yesterday }
- let(:second) { FactoryGirl.create :ci_build, commit: commit, status: 'pending' }
- before { first; second }
+ let!(:first) { create(:ci_build, commit: commit, status: 'pending', created_at: Date.yesterday) }
+ let!(:second) { create(:ci_build, commit: commit, status: 'pending') }
subject { Ci::Build.first_pending }
it { is_expected.to be_a(Ci::Build) }
@@ -90,7 +89,7 @@ describe Ci::Build, models: true do
build.update_attributes(trace: token)
end
- it { is_expected.to_not include(token) }
+ it { is_expected.not_to include(token) }
end
end
@@ -219,8 +218,8 @@ describe Ci::Build, models: true do
it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
context 'and trigger variables' do
- let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
- let(:trigger_request) { FactoryGirl.create :ci_trigger_request_with_variables, commit: commit, trigger: trigger }
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger_request) { create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) }
let(:trigger_variables) do
[
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
@@ -259,11 +258,11 @@ describe Ci::Build, models: true do
end
describe '#can_be_served?' do
- let(:runner) { FactoryGirl.create :ci_runner }
+ let(:runner) { create(:ci_runner) }
before { build.project.runners << runner }
- context 'runner without tags' do
+ context 'when runner does not have tags' do
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
@@ -274,25 +273,53 @@ describe Ci::Build, models: true do
end
end
- context 'runner with tags' do
+ context 'when runner has tags' do
before { runner.tag_list = ['bb', 'cc'] }
- it 'can handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_truthy
+ shared_examples 'tagged build picker' do
+ it 'can handle build with matching tags' do
+ build.tag_list = ['bb']
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'cannot handle build without matching tags' do
+ build.tag_list = ['aa']
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
end
- it 'can handle build with matching tags' do
- build.tag_list = ['bb']
- expect(build.can_be_served?(runner)).to be_truthy
+ context 'when runner can pick untagged jobs' do
+ it 'can handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it_behaves_like 'tagged build picker'
end
- it 'cannot handle build with not matching tags' do
- build.tag_list = ['aa']
- expect(build.can_be_served?(runner)).to be_falsey
+ context 'when runner can not pick untagged jobs' do
+ before { runner.run_untagged = false }
+
+ it 'can not handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
+
+ it_behaves_like 'tagged build picker'
end
end
end
+ describe '#has_tags?' do
+ context 'when build has tags' do
+ subject { create(:ci_build, tag_list: ['tag']) }
+ it { is_expected.to have_tags }
+ end
+
+ context 'when build does not have tags' do
+ subject { create(:ci_build, tag_list: []) }
+ it { is_expected.not_to have_tags }
+ end
+ end
+
describe '#any_runners_online?' do
subject { build.any_runners_online? }
@@ -301,7 +328,7 @@ describe Ci::Build, models: true do
end
context 'if there are runner' do
- let(:runner) { FactoryGirl.create :ci_runner }
+ let(:runner) { create(:ci_runner) }
before do
build.project.runners << runner
@@ -338,7 +365,7 @@ describe Ci::Build, models: true do
it { is_expected.to be_truthy }
context "and there are specific runner" do
- let(:runner) { FactoryGirl.create :ci_runner, contacted_at: 1.second.ago }
+ let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
before do
build.project.runners << runner
@@ -387,7 +414,7 @@ describe Ci::Build, models: true do
end
describe '#repo_url' do
- let(:build) { FactoryGirl.create :ci_build }
+ let(:build) { create(:ci_build) }
let(:project) { build.project }
subject { build.repo_url }
@@ -401,10 +428,10 @@ describe Ci::Build, models: true do
end
describe '#depends_on_builds' do
- let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
- let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
- let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
- let!(:staging) { FactoryGirl.create :ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy' }
+ let!(:build) { create(:ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build') }
+ let!(:rspec_test) { create(:ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test') }
+ let!(:rubocop_test) { create(:ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test') }
+ let!(:staging) { create(:ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy') }
it 'to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
@@ -425,11 +452,10 @@ describe Ci::Build, models: true do
end
def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
- FactoryGirl.create(factory,
- source_project_id: commit.gl_project_id,
- target_project_id: commit.gl_project_id,
- source_branch: build.ref,
- created_at: created_at)
+ create(factory, source_project_id: commit.gl_project_id,
+ target_project_id: commit.gl_project_id,
+ source_branch: build.ref,
+ created_at: created_at)
end
describe '#merge_request' do
@@ -473,8 +499,8 @@ describe Ci::Build, models: true do
context 'when a Build is created after the MR' do
before do
@merge_request = create_mr(build, commit, factory: :merge_request_with_diffs)
- commit2 = FactoryGirl.create :ci_commit, project: project
- @build2 = FactoryGirl.create :ci_build, commit: commit2
+ commit2 = create(:ci_commit, project: project)
+ @build2 = create(:ci_build, commit: commit2)
commits = [double(id: commit.sha), double(id: commit2.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
@@ -506,7 +532,7 @@ describe Ci::Build, models: true do
end
it 'should set erase date' do
- expect(build.erased_at).to_not be_falsy
+ expect(build.erased_at).not_to be_falsy
end
end
@@ -578,7 +604,7 @@ describe Ci::Build, models: true do
describe '#erase' do
it 'should not raise error' do
- expect { build.erase }.to_not raise_error
+ expect { build.erase }.not_to raise_error
end
end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index a747aa08447..22f8639e5ab 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_commits
-#
-# id :integer not null, primary key
-# project_id :integer
-# ref :string(255)
-# sha :string(255)
-# before_sha :string(255)
-# push_data :text
-# created_at :datetime
-# updated_at :datetime
-# tag :boolean default(FALSE)
-# yaml_errors :text
-# committed_at :datetime
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Commit, models: true do
@@ -28,7 +10,6 @@ describe Ci::Commit, models: true do
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status }
- it { is_expected.to delegate_method(:stages).to(:statuses) }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
@@ -266,7 +247,7 @@ describe Ci::Commit, models: true do
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.running_or_pending).to_not be_empty
+ expect(commit.builds.running_or_pending).not_to be_empty
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb
deleted file mode 100644
index 000a732db77..00000000000
--- a/spec/models/ci/runner_project_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_runner_projects
-#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
-require 'spec_helper'
-
-describe Ci::RunnerProject, models: true do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 25e9e5eca48..5d04d8ffcff 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1,25 +1,24 @@
-# == Schema Information
-#
-# Table name: ci_runners
-#
-# id :integer not null, primary key
-# token :string(255)
-# created_at :datetime
-# updated_at :datetime
-# description :string(255)
-# 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)
-#
-
require 'spec_helper'
describe Ci::Runner, models: true do
+ describe 'validation' do
+ context 'when runner is not allowed to pick untagged jobs' do
+ context 'when runner does not have tags' do
+ it 'is not valid' do
+ runner = build(:ci_runner, tag_list: [], run_untagged: false)
+ expect(runner).to be_invalid
+ end
+ end
+
+ context 'when runner has tags' do
+ it 'is valid' do
+ runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false)
+ expect(runner).to be_valid
+ end
+ end
+ end
+ end
+
describe '#display_name' do
it 'should return the description if it has a value' do
runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
@@ -133,7 +132,19 @@ describe Ci::Runner, models: true do
end
end
- describe '#search' do
+ describe '#has_tags?' do
+ context 'when runner has tags' do
+ subject { create(:ci_runner, tag_list: ['tag']) }
+ it { is_expected.to have_tags }
+ end
+
+ context 'when runner does not have tags' do
+ subject { create(:ci_runner, tag_list: []) }
+ it { is_expected.not_to have_tags }
+ end
+ end
+
+ describe '.search' do
let(:runner) { create(:ci_runner, token: '123abc') }
it 'returns runners with a matching token' do
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 159be939300..474b0b1621d 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_triggers
-#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Trigger, models: true do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 71e84091cb7..98f60087cf5 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-# id :integer not null, primary key
-# project_id :integer
-# key :string(255)
-# value :text
-# encrypted_value :text
-# encrypted_value_salt :string(255)
-# encrypted_value_iv :string(255)
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Variable, models: true do
@@ -37,7 +23,7 @@ describe Ci::Variable, models: true do
end
it 'fails to decrypt if iv is incorrect' do
- subject.encrypted_value_iv = nil
+ subject.encrypted_value_iv = SecureRandom.hex
subject.instance_variable_set(:@value, nil)
expect { subject.value }.
to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 9307d97e214..384a38ebc69 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -24,6 +24,16 @@ describe CommitRange, models: true do
expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
end
+ describe '#initialize' do
+ it 'does not modify strings in-place' do
+ input = "#{sha_from}...#{sha_to} "
+
+ described_class.new(input, project)
+
+ expect(input).to eq("#{sha_from}...#{sha_to} ")
+ end
+ end
+
describe '#to_s' do
it 'is correct for three-dot syntax' do
expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
@@ -135,4 +145,28 @@ describe CommitRange, models: true do
end
end
end
+
+ describe '#has_been_reverted?' do
+ it 'returns true if the commit has been reverted' do
+ issue = create(:issue)
+
+ create(:note_on_issue,
+ noteable: issue,
+ system: true,
+ note: commit1.revert_description,
+ project: issue.project)
+
+ expect_any_instance_of(Commit).to receive(:reverts_commit?).
+ with(commit1).
+ and_return(true)
+
+ expect(commit1.has_been_reverted?(nil, issue)).to eq(true)
+ end
+
+ it 'returns false a commit has not been reverted' do
+ issue = create(:issue)
+
+ expect(commit1.has_been_reverted?(nil, issue)).to eq(false)
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ad47e338a33..beca8708c9d 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Commit, models: true do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:commit) { project.commit }
describe 'modules' do
@@ -56,7 +56,7 @@ describe Commit, models: true do
end
it "does not truncates a message with a newline after 80 but less 100 characters" do
- message =<<eos
+ message = <<eos
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit.
Vivamus egestas lacinia lacus, sed rutrum mauris.
eos
@@ -171,4 +171,40 @@ eos
describe '#status' do
# TODO: kamil
end
+
+ describe '#participants' do
+ let(:user1) { build(:user) }
+ let(:user2) { build(:user) }
+
+ let!(:note1) do
+ create(:note_on_commit,
+ commit_id: commit.id,
+ project: project,
+ note: 'foo')
+ end
+
+ let!(:note2) do
+ create(:note_on_commit,
+ commit_id: commit.id,
+ project: project,
+ note: 'bar')
+ end
+
+ before do
+ allow(commit).to receive(:author).and_return(user1)
+ allow(commit).to receive(:committer).and_return(user2)
+ end
+
+ it 'includes the commit author' do
+ expect(commit.participants).to include(commit.author)
+ end
+
+ it 'includes the committer' do
+ expect(commit.participants).to include(commit.committer)
+ end
+
+ it 'includes the authors of the commit notes' do
+ expect(commit.participants).to include(note1.author, note2.author)
+ end
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 971e6750375..434e58cfd06 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,37 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe CommitStatus, models: true do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 4a4cd093435..e9f827e9f50 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -114,6 +114,35 @@ describe Issue, "Issuable" do
end
end
+ describe "#sort" do
+ let(:project) { build_stubbed(:empty_project) }
+
+ context "by milestone due date" do
+ # Correct order is:
+ # Issues/MRs with milestones ordered by date
+ # Issues/MRs with milestones without dates
+ # Issues/MRs without milestones
+
+ let!(:issue) { create(:issue, project: project) }
+ let!(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
+ let!(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
+ let!(:issue1) { create(:issue, project: project, milestone: early_milestone) }
+ let!(:issue2) { create(:issue, project: project, milestone: late_milestone) }
+ let!(:issue3) { create(:issue, project: project) }
+
+ it "sorts desc" do
+ issues = project.issues.sort('milestone_due_desc')
+ expect(issues).to match_array([issue2, issue1, issue, issue3])
+ end
+
+ it "sorts asc" do
+ issues = project.issues.sort('milestone_due_asc')
+ expect(issues).to match_array([issue1, issue2, issue, issue3])
+ end
+ end
+ end
+
+
describe '#subscribed?' do
context 'user is not a participant in the issue' do
before { allow(issue).to receive(:participants).with(user).and_return([]) }
@@ -160,12 +189,11 @@ describe Issue, "Issuable" do
let(:data) { issue.to_hook_data(user) }
let(:project) { issue.project }
-
it "returns correct hook data" do
expect(data[:object_kind]).to eq("issue")
expect(data[:user]).to eq(user.hook_attrs)
expect(data[:object_attributes]).to eq(issue.hook_attrs)
- expect(data).to_not have_key(:assignee)
+ expect(data).not_to have_key(:assignee)
end
context "issue is assigned" do
@@ -199,12 +227,26 @@ describe Issue, "Issuable" do
end
end
+ describe '#labels_array' do
+ let(:project) { create(:project) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:issue) { create(:issue, project: project) }
+
+ before(:each) do
+ issue.labels << bug
+ end
+
+ it 'loads the association and returns it as an array' do
+ expect(issue.reload.labels_array).to eq([bug])
+ end
+ end
+
describe "votes" do
+ let(:project) { issue.project }
+
before do
- author = create :user
- project = create :empty_project
- issue.notes.awards.create!(note: "thumbsup", author: author, project: project)
- issue.notes.awards.create!(note: "thumbsdown", author: author, project: project)
+ issue.notes.awards.create!(note: "thumbsup", author: user, project: project)
+ issue.notes.awards.create!(note: "thumbsdown", author: user, project: project)
end
it "returns correct values" do
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
new file mode 100644
index 00000000000..7e4ea0f2d66
--- /dev/null
+++ b/spec/models/concerns/participable_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe Participable, models: true do
+ let(:model) do
+ Class.new do
+ include Participable
+ end
+ end
+
+ describe '.participant' do
+ it 'adds the participant attributes to the existing list' do
+ model.participant(:foo)
+ model.participant(:bar)
+
+ expect(model.participant_attrs).to eq([:foo, :bar])
+ end
+ end
+
+ describe '#participants' do
+ it 'returns the list of participants' do
+ model.participant(:foo)
+ model.participant(:bar)
+
+ user1 = build(:user)
+ user2 = build(:user)
+ user3 = build(:user)
+ project = build(:project, :public)
+ instance = model.new
+
+ expect(instance).to receive(:foo).and_return(user2)
+ expect(instance).to receive(:bar).and_return(user3)
+ expect(instance).to receive(:project).twice.and_return(project)
+
+ participants = instance.participants(user1)
+
+ expect(participants).to include(user2)
+ expect(participants).to include(user3)
+ end
+
+ it 'supports attributes returning another Participable' do
+ other_model = Class.new { include Participable }
+
+ other_model.participant(:bar)
+ model.participant(:foo)
+
+ instance = model.new
+ other = other_model.new
+ user1 = build(:user)
+ user2 = build(:user)
+ project = build(:project, :public)
+
+ expect(instance).to receive(:foo).and_return(other)
+ expect(other).to receive(:bar).and_return(user2)
+ expect(instance).to receive(:project).twice.and_return(project)
+
+ expect(instance.participants(user1)).to eq([user2])
+ end
+
+ context 'when using a Proc as an attribute' do
+ it 'calls the supplied Proc' do
+ user1 = build(:user)
+ project = build(:project, :public)
+
+ user_arg = nil
+ ext_arg = nil
+
+ model.participant -> (user, ext) do
+ user_arg = user
+ ext_arg = ext
+ end
+
+ instance = model.new
+
+ expect(instance).to receive(:project).twice.and_return(project)
+
+ instance.participants(user1)
+
+ expect(user_arg).to eq(user1)
+ expect(ext_arg).to be_an_instance_of(Gitlab::ReferenceExtractor)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index e31fdb0bffb..b7fc5a92497 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -44,6 +44,16 @@ describe Subscribable, 'Subscribable' do
end
end
+ describe '#subscribe' do
+ it 'subscribes the given user' do
+ expect(resource.subscribed?(user)).to be_falsey
+
+ resource.subscribe(user)
+
+ expect(resource.subscribed?(user)).to be_truthy
+ end
+ end
+
describe '#unsubscribe' do
it 'unsubscribes the given current user' do
resource.subscriptions.create(user: user, subscribed: true)
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 30c0a04b840..9e8ebc56a31 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -28,14 +28,14 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
context 'token is not generated yet' do
describe 'token field accessor' do
subject { described_class.new.send(token_field) }
- it { is_expected.to_not be_blank }
+ it { is_expected.not_to be_blank }
end
describe 'ensured token' do
subject { described_class.new.send("ensure_#{token_field}") }
it { is_expected.to be_a String }
- it { is_expected.to_not be_blank }
+ it { is_expected.not_to be_blank }
end
describe 'ensured! token' do
@@ -49,7 +49,7 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
context 'token is generated' do
before { subject.send("reset_#{token_field}!") }
- it 'persists a new token 'do
+ it 'persists a new token' do
expect(subject.send(:read_attribute, token_field)).to be_a String
end
end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 64ba778afea..6a90598a629 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe DeployKey, models: true do
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 8aedbfb8636..8a1e337c1a3 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: deploy_keys_projects
-#
-# id :integer not null, primary key
-# deploy_key_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe DeployKeysProject, models: true do
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index a20a6149649..5d0bd31db5a 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: emails
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# email :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe Email, models: true do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 0c3cd13f399..b0e76fec693 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: events
-#
-# id :integer not null, primary key
-# target_type :string(255)
-# target_id :integer
-# title :string(255)
-# data :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# action :integer
-# author_id :integer
-#
-
require 'spec_helper'
describe Event, models: true do
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index d90fbfe1ea5..3b817608ce0 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 5b0883d8702..d0e02618b6b 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,37 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe GenericCommitStatus, models: true do
@@ -61,13 +27,13 @@ describe GenericCommitStatus, models: true do
describe :context do
subject { generic_commit_status.context }
- it { is_expected.to_not be_nil }
+ it { is_expected.not_to be_nil }
end
describe :stage do
subject { generic_commit_status.stage }
- it { is_expected.to_not be_nil }
+ it { is_expected.not_to be_nil }
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 7bfca1e72c3..6fa16be7f04 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# 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)
-#
-
require 'spec_helper'
describe Group, models: true do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index f800f415bd2..534e1b4f128 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -34,14 +34,14 @@ describe ServiceHook, models: true do
it "POSTs to the webhook URL" do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
).once
end
it "POSTs the data as JSON" do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
).once
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 56a9fbe9720..4078b9e4ff5 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -33,7 +33,7 @@ describe SystemHook, models: true do
Projects::CreateService.new(user, name: 'empty').execute
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /project_create/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -42,7 +42,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /project_destroy/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -51,7 +51,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_create/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -60,7 +60,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_destroy/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -69,7 +69,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_add_to_team/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -79,7 +79,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_remove_from_team/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -88,7 +88,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /group_create/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -97,7 +97,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /group_destroy/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -106,7 +106,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_add_to_group/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
@@ -116,7 +116,7 @@ describe SystemHook, models: true do
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /user_remove_from_group/,
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 37a27d73aab..f9bab487b96 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -95,13 +95,13 @@ describe WebHook, models: true do
it "handles 200 status code" do
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([200, 'Success'])
end
it "handles 2xx status codes" do
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([201, 'Success'])
end
end
end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 5afe042e154..1b987588f59 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: identities
-#
-# id :integer not null, primary key
-# extern_uid :string(255)
-# provider :string(255)
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
RSpec.describe Identity, models: true do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 060e6599104..b87d68283e6 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: issues
-#
-# id :integer not null, primary key
-# title :string(255)
-# assignee_id :integer
-# author_id :integer
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# position :integer default(0)
-# branch_name :string(255)
-# description :text
-# milestone_id :integer
-# state :string(255)
-# iid :integer
-# updated_by_id :integer
-#
-
require 'spec_helper'
describe Issue, models: true do
@@ -212,7 +192,7 @@ describe Issue, models: true do
source_project: subject.project,
source_branch: "#{subject.iid}-branch" })
merge_request.create_cross_references!(user)
- expect(subject.referenced_merge_requests).to_not be_empty
+ expect(subject.referenced_merge_requests).not_to be_empty
expect(subject.related_branches(user)).to eq([subject.to_branch_name])
end
@@ -251,4 +231,59 @@ describe Issue, models: true do
expect(issue.to_branch_name).to match /confidential-issue\z/
end
end
+
+ describe '#participants' do
+ context 'using a public project' do
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+
+ let!(:note1) do
+ create(:note_on_issue, noteable: issue, project: project, note: 'a')
+ end
+
+ let!(:note2) do
+ create(:note_on_issue, noteable: issue, project: project, note: 'b')
+ end
+
+ it 'includes the issue author' do
+ expect(issue.participants).to include(issue.author)
+ end
+
+ it 'includes the authors of the notes' do
+ expect(issue.participants).to include(note1.author, note2.author)
+ end
+ end
+
+ context 'using a private project' do
+ it 'does not include mentioned users that do not have access to the project' do
+ project = create(:project)
+ user = create(:user)
+ issue = create(:issue, project: project)
+
+ create(:note_on_issue,
+ noteable: issue,
+ project: project,
+ note: user.to_reference)
+
+ expect(issue.participants).not_to include(user)
+ end
+ end
+ end
+
+ describe 'cached counts' do
+ it 'updates when assignees change' do
+ user1 = create(:user)
+ user2 = create(:user)
+ issue = create(:issue, assignee: user1)
+
+ expect(user1.assigned_open_issues_count).to eq(1)
+ expect(user2.assigned_open_issues_count).to eq(0)
+
+ issue.assignee = user2
+ issue.save
+
+ expect(user1.assigned_open_issues_count).to eq(0)
+ expect(user2.assigned_open_issues_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index c962b83644a..26fbedbef2f 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Key, models: true do
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index dc7510b1de3..5e6f8ca1528 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string(255)
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe LabelLink, models: true do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 0614ca1e7c9..dad2628651b 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# 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)
-#
-
require 'spec_helper'
describe Label, models: true do
@@ -55,6 +42,14 @@ describe Label, models: true do
end
end
+ describe "#title" do
+ let(:label) { create(:label, title: "<b>test</b>") }
+
+ it "sanitizes title" do
+ expect(label.title).to eq("test")
+ end
+ end
+
describe '#to_reference' do
context 'using id' do
it 'returns a String reference to the object' do
diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb
new file mode 100644
index 00000000000..b2d06853886
--- /dev/null
+++ b/spec/models/legacy_diff_note_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe LegacyDiffNote, models: true do
+ describe "Commit diff line notes" do
+ let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
+ let!(:commit) { note.noteable }
+
+ it "should save a valid note" do
+ expect(note.commit_id).to eq(commit.id)
+ expect(note.noteable.id).to eq(commit.id)
+ end
+
+ it "should be recognized by #legacy_diff_note?" do
+ expect(note).to be_legacy_diff_note
+ end
+ end
+
+ describe '#active?' do
+ it 'is always true when the note has no associated diff' do
+ note = build(:note_on_merge_request_diff)
+
+ expect(note).to receive(:diff).and_return(nil)
+
+ expect(note).to be_active
+ end
+
+ it 'is never true when the note has no noteable associated' do
+ note = build(:note_on_merge_request_diff)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'returns the memoized value if defined' do
+ note = build(:note_on_merge_request_diff)
+
+ note.instance_variable_set(:@active, 'foo')
+ expect(note).not_to receive(:find_noteable_diff)
+
+ expect(note.active?).to eq 'foo'
+ end
+
+ context 'for a merge request noteable' do
+ it 'is false when noteable has no matching diff' do
+ merge = build_stubbed(:merge_request, :simple)
+ note = build(:note_on_merge_request_diff, noteable: merge)
+
+ allow(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:find_noteable_diff).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'is true when noteable has a matching diff' do
+ merge = create(:merge_request, :simple)
+
+ # Generate a real line_code value so we know it will match. We use a
+ # random line from a random diff just for funsies.
+ diff = merge.diffs.to_a.sample
+ line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
+ code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+
+ # We're persisting in order to trigger the set_diff callback
+ note = create(:note_on_merge_request_diff, noteable: merge,
+ line_code: code,
+ project: merge.source_project)
+
+ # Make sure we don't get a false positive from a guard clause
+ expect(note).to receive(:find_noteable_diff).and_call_original
+ expect(note).to be_active
+ end
+ end
+ end
+end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 2d8f1cc1ad3..6e51730eecd 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string(255) not null
-# user_id :integer
-# notification_level :integer not null
-# type :string(255)
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
-# invite_accepted_at :datetime
-#
-
require 'spec_helper'
describe Member, models: true do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 9f26d9eb5ce..9f13874b532 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -20,6 +20,48 @@
require 'spec_helper'
describe ProjectMember, models: true do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project).class_name('Project').with_foreign_key(:source_id) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to allow_value('Project').for(:source_type) }
+ it { is_expected.not_to allow_value('project').for(:source_type) }
+ end
+
+ describe 'modules' do
+ it { is_expected.to include_module(Gitlab::ShellAdapter) }
+ end
+
+ describe "#destroy" do
+ let(:owner) { create(:project_member, access_level: ProjectMember::OWNER) }
+ let(:project) { owner.project }
+ let(:master) { create(:project_member, project: project) }
+
+ let(:owner_todos) { (0...2).map { create(:todo, user: owner.user, project: project) } }
+ let(:master_todos) { (0...3).map { create(:todo, user: master.user, project: project) } }
+
+ before do
+ owner_todos
+ master_todos
+ end
+
+ it "destroy itself and delete associated todos" do
+ expect(owner.user.todos.size).to eq(2)
+ expect(master.user.todos.size).to eq(3)
+ expect(Todo.count).to eq(5)
+
+ master_todo_ids = master_todos.map(&:id)
+ master.destroy
+
+ expect(owner.user.todos.size).to eq(2)
+ expect(Todo.count).to eq(2)
+ master_todo_ids.each do |id|
+ expect(Todo.exists?(id)).to eq(false)
+ end
+ end
+ end
+
describe :import_team do
before do
@abilities = Six.new
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index d7884cea336..a4c55cc2fd0 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,32 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# 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_params :text
-# merge_when_build_succeeds :boolean default(FALSE), not null
-# merge_user_id :integer
-# merge_commit_sha :string
-#
-
require 'spec_helper'
describe MergeRequest, models: true do
@@ -93,7 +64,13 @@ describe MergeRequest, models: true do
describe '#target_sha' do
context 'when the target branch does not exist anymore' do
- subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+ let(:project) { create(:project) }
+
+ subject { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ project.repository.raw_repository.delete_branch(subject.target_branch)
+ end
it 'returns nil' do
expect(subject.target_sha).to be_nil
@@ -142,7 +119,8 @@ describe MergeRequest, models: true do
before do
allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
- create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.project)
+ create(:note_on_commit, commit_id: merge_request.commits.first.id,
+ project: merge_request.project)
create(:note, noteable: merge_request, project: merge_request.project)
end
@@ -152,7 +130,9 @@ describe MergeRequest, models: true do
end
it "should include notes for commits from target project as well" do
- create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.target_project)
+ create(:note_on_commit, commit_id: merge_request.commits.first.id,
+ project: merge_request.target_project)
+
expect(merge_request.commits).not_to be_empty
expect(merge_request.mr_and_commit_notes.count).to eq(3)
end
@@ -283,13 +263,18 @@ describe MergeRequest, models: true do
end
describe "#reset_merge_when_build_succeeds" do
- let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user) }
+ let(:merge_if_green) do
+ create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
+ merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
+ end
it "sets the item to false" do
merge_if_green.reset_merge_when_build_succeeds
merge_if_green.reload
expect(merge_if_green.merge_when_build_succeeds).to be_falsey
+ expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
+ expect(merge_if_green.merge_params["commit_message"]).to be_nil
end
end
@@ -318,7 +303,12 @@ describe MergeRequest, models: true do
let(:fork_project) { create(:project, forked_from_project: project) }
context 'when the target branch does not exist anymore' do
- subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }
+ subject { create(:merge_request, source_project: project, target_project: project) }
+
+ before do
+ project.repository.raw_repository.delete_branch(subject.target_branch)
+ subject.reload
+ end
it 'does not crash' do
expect{ subject.diverged_commits_count }.not_to raise_error
@@ -424,4 +414,45 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#participants' do
+ let(:project) { create(:project, :public) }
+
+ let(:mr) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+
+ let!(:note1) do
+ create(:note_on_merge_request, noteable: mr, project: project, note: 'a')
+ end
+
+ let!(:note2) do
+ create(:note_on_merge_request, noteable: mr, project: project, note: 'b')
+ end
+
+ it 'includes the merge request author' do
+ expect(mr.participants).to include(mr.author)
+ end
+
+ it 'includes the authors of the notes' do
+ expect(mr.participants).to include(note1.author, note2.author)
+ end
+ end
+
+ describe 'cached counts' do
+ it 'updates when assignees change' do
+ user1 = create(:user)
+ user2 = create(:user)
+ mr = create(:merge_request, assignee: user1)
+
+ expect(user1.assigned_open_merge_request_count).to eq(1)
+ expect(user2.assigned_open_merge_request_count).to eq(0)
+
+ mr.assignee = user2
+ mr.save
+
+ expect(user1.assigned_open_merge_request_count).to eq(0)
+ expect(user2.assigned_open_merge_request_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 72a4ea70228..1e18c788b50 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: milestones
-#
-# id :integer not null, primary key
-# title :string(255) not null
-# project_id :integer not null
-# description :text
-# due_date :date
-# created_at :datetime
-# updated_at :datetime
-# state :string(255)
-# iid :integer
-#
-
require 'spec_helper'
describe Milestone, models: true do
@@ -34,6 +19,14 @@ describe Milestone, models: true do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
+ describe "#title" do
+ let(:milestone) { create(:milestone, title: "<b>test</b>") }
+
+ it "sanitizes title" do
+ expect(milestone.title).to eq("test")
+ end
+ end
+
describe "unique milestone title per project" do
it "shouldn't accept the same title in a project twice" do
new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
@@ -211,4 +204,37 @@ describe Milestone, models: true do
to eq([milestone])
end
end
+
+ describe '.upcoming_ids_by_projects' do
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:project_3) { create(:empty_project) }
+ let(:projects) { [project_1, project_2, project_3] }
+
+ let!(:past_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now - 1.day) }
+ let!(:current_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 1.day) }
+ let!(:future_milestone_project_1) { create(:milestone, project: project_1, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now - 1.day) }
+ let!(:closed_milestone_project_2) { create(:milestone, :closed, project: project_2, due_date: Time.now + 1.day) }
+ let!(:current_milestone_project_2) { create(:milestone, project: project_2, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) }
+
+ # The call to `#try` is because this returns a relation with a Postgres DB,
+ # and an array of IDs with a MySQL DB.
+ let(:milestone_ids) { Milestone.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
+
+ it 'returns the next upcoming open milestone ID for each project' do
+ expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
+ end
+
+ context 'when the projects have no open upcoming milestones' do
+ let(:projects) { [project_3] }
+
+ it 'returns no results' do
+ expect(milestone_ids).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 3c3a580942a..4e68ac5e63a 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# 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)
-#
-
require 'spec_helper'
describe Namespace, models: true do
@@ -85,6 +70,20 @@ describe Namespace, models: true do
allow(@namespace).to receive(:path).and_return(new_path)
expect(@namespace.move_dir).to be_truthy
end
+
+ context "when any project has container tags" do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags('tag')
+
+ create(:empty_project, namespace: @namespace)
+
+ allow(@namespace).to receive(:path_was).and_return(@namespace.path)
+ allow(@namespace).to receive(:path).and_return('new_path')
+ end
+
+ it { expect { @namespace.move_dir }.to raise_error('Namespace cannot be moved, because at least one project has tags in container registry') }
+ end
end
describe :rm_dir do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 6b18936edb1..e9d89c9a847 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string(255)
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-# is_award :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Note, models: true do
@@ -33,6 +12,34 @@ describe Note, models: true do
describe 'validation' do
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
+
+ context 'when note is on commit' do
+ before { allow(subject).to receive(:for_commit?).and_return(true) }
+
+ it { is_expected.to validate_presence_of(:commit_id) }
+ it { is_expected.not_to validate_presence_of(:noteable_id) }
+ end
+
+ context 'when note is not on commit' do
+ before { allow(subject).to receive(:for_commit?).and_return(false) }
+
+ it { is_expected.not_to validate_presence_of(:commit_id) }
+ it { is_expected.to validate_presence_of(:noteable_id) }
+ end
+
+ context 'when noteable and note project differ' do
+ subject do
+ build(:note, noteable: build_stubbed(:issue),
+ project: build_stubbed(:project))
+ end
+
+ it { is_expected.to be_invalid }
+ end
+
+ context 'when noteable and note project are the same' do
+ subject { create(:note) }
+ it { is_expected.to be_valid }
+ end
end
describe "Commit notes" do
@@ -55,24 +62,6 @@ describe Note, models: true do
end
end
- describe "Commit diff line notes" do
- let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
- let!(:commit) { note.noteable }
-
- it "should save a valid note" do
- expect(note.commit_id).to eq(commit.id)
- expect(note.noteable.id).to eq(commit.id)
- end
-
- it "should be recognized by #for_diff_line?" do
- expect(note).to be_for_diff_line
- end
-
- it "should be recognized by #for_commit_diff_line?" do
- expect(note).to be_for_commit_diff_line
- end
- end
-
describe 'authorization' do
before do
@p1 = create(:project)
@@ -128,12 +117,23 @@ describe Note, models: true do
end
describe "#all_references" do
- let!(:note1) { create(:note) }
- let!(:note2) { create(:note) }
+ let!(:note1) { create(:note_on_issue) }
+ let!(:note2) { create(:note_on_issue) }
it "reads the rendered note body from the cache" do
- expect(Banzai::Renderer).to receive(:render).with(note1.note, pipeline: :note, cache_key: [note1, "note"], project: note1.project)
- expect(Banzai::Renderer).to receive(:render).with(note2.note, pipeline: :note, cache_key: [note2, "note"], project: note2.project)
+ expect(Banzai::Renderer).to receive(:render).
+ with(note1.note,
+ pipeline: :note,
+ cache_key: [note1, "note"],
+ project: note1.project,
+ author: note1.author)
+
+ expect(Banzai::Renderer).to receive(:render).
+ with(note2.note,
+ pipeline: :note,
+ cache_key: [note2, "note"],
+ project: note2.project,
+ author: note2.author)
note1.all_references
note2.all_references
@@ -141,7 +141,7 @@ describe Note, models: true do
end
describe '.search' do
- let(:note) { create(:note, note: 'WoW') }
+ let(:note) { create(:note_on_issue, note: 'WoW') }
it 'returns notes with matching content' do
expect(described_class.search(note.note)).to eq([note])
@@ -150,6 +150,25 @@ describe Note, models: true do
it 'returns notes with matching content regardless of the casing' do
expect(described_class.search('WOW')).to eq([note])
end
+
+ context "confidential issues" do
+ let(:user) { create :user }
+ let(:confidential_issue) { create(:issue, :confidential, author: user) }
+ let(:confidential_note) { create :note, note: "Random", noteable: confidential_issue, project: confidential_issue.project }
+
+ it "returns notes with matching content if user can see the issue" do
+ expect(described_class.search(confidential_note.note, as_user: user)).to eq([confidential_note])
+ end
+
+ it "does not return notes with matching content if user can not see the issue" do
+ user = create :user
+ expect(described_class.search(confidential_note.note, as_user: user)).to be_empty
+ end
+
+ it "does not return notes with matching content for unauthenticated users" do
+ expect(described_class.search(confidential_note.note)).to be_empty
+ end
+ end
end
describe '.grouped_awards' do
@@ -169,66 +188,6 @@ describe Note, models: true do
end
end
- describe '#active?' do
- it 'is always true when the note has no associated diff' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(nil)
-
- expect(note).to be_active
- end
-
- it 'is never true when the note has no noteable associated' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(double)
- expect(note).to receive(:noteable).and_return(nil)
-
- expect(note).not_to be_active
- end
-
- it 'returns the memoized value if defined' do
- note = build(:note)
-
- expect(note).to receive(:diff).and_return(double)
- expect(note).to receive(:noteable).and_return(double)
-
- note.instance_variable_set(:@active, 'foo')
- expect(note).not_to receive(:find_noteable_diff)
-
- expect(note.active?).to eq 'foo'
- end
-
- context 'for a merge request noteable' do
- it 'is false when noteable has no matching diff' do
- merge = build_stubbed(:merge_request, :simple)
- note = build(:note, noteable: merge)
-
- allow(note).to receive(:diff).and_return(double)
- expect(note).to receive(:find_noteable_diff).and_return(nil)
-
- expect(note).not_to be_active
- end
-
- it 'is true when noteable has a matching diff' do
- merge = create(:merge_request, :simple)
-
- # Generate a real line_code value so we know it will match. We use a
- # random line from a random diff just for funsies.
- diff = merge.diffs.to_a.sample
- line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
- code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
-
- # We're persisting in order to trigger the set_diff callback
- note = create(:note, noteable: merge, line_code: code)
-
- # Make sure we don't get a false positive from a guard clause
- expect(note).to receive(:find_noteable_diff).and_call_original
- expect(note).to be_active
- end
- end
- end
-
describe "editable?" do
it "returns true" do
note = build(:note)
@@ -274,12 +233,18 @@ describe Note, models: true do
let(:merge_request) { create :merge_request }
it "converts aliases to actual name" do
- note = create(:note, note: ":+1:", noteable: merge_request)
+ note = create(:note, note: ":+1:",
+ noteable: merge_request,
+ project: merge_request.project)
+
expect(note.reload.note).to eq("thumbsup")
end
it "is not an award emoji when comment is on a diff" do
- note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
+ note = create(:note_on_merge_request_diff, note: ":blowfish:",
+ noteable: merge_request,
+ project: merge_request.project,
+ line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
note = note.reload
expect(note.note).to eq(":blowfish:")
@@ -294,4 +259,14 @@ describe Note, models: true do
expect { note.valid? }.to change(note, :line_code).to(nil)
end
end
+
+ describe '#participants' do
+ it 'includes the note author' do
+ project = create(:project, :public)
+ issue = create(:issue, project: project)
+ note = create(:note_on_issue, noteable: issue, project: project)
+
+ expect(note.participants).to include(note.author)
+ end
+ end
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 6fb5cad5011..5f618322aab 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -176,86 +176,117 @@ describe HipchatService, models: true do
context "Note events" do
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id) }
- let(:issue) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:snippet) { create(:project_snippet, project: project) }
- let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
- let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") }
- let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")}
- let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") }
-
- it "should call Hipchat API for commit comment events" do
- data = Gitlab::NoteDataBuilder.build(commit_note, user)
- hipchat.execute(data)
- expect(WebMock).to have_requested(:post, api_url).once
+ context 'when commit comment event triggered' do
+ let(:commit_note) do
+ create(:note_on_commit, author: user, project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit')
+ end
+
+ it "should call Hipchat API for commit comment events" do
+ data = Gitlab::NoteDataBuilder.build(commit_note, user)
+ hipchat.execute(data)
- message = hipchat.send(:create_message, data)
+ expect(WebMock).to have_requested(:post, api_url).once
- obj_attr = data[:object_attributes]
- commit_id = Commit.truncate_sha(data[:commit][:id])
- title = hipchat.send(:format_title, data[:commit][:message])
+ message = hipchat.send(:create_message, data)
- expect(message).to eq("#{user.name} commented on " \
- "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
- "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
- "#{title}" \
- "<pre>a comment on a commit</pre>")
+ obj_attr = data[:object_attributes]
+ commit_id = Commit.truncate_sha(data[:commit][:id])
+ title = hipchat.send(:format_title, data[:commit][:message])
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "#{title}" \
+ "<pre>a comment on a commit</pre>")
+ end
end
- it "should call Hipchat API for merge request comment events" do
- data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
- hipchat.execute(data)
+ context 'when merge request comment event triggered' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
- expect(WebMock).to have_requested(:post, api_url).once
+ let(:merge_request_note) do
+ create(:note_on_merge_request, noteable: merge_request,
+ project: project,
+ note: "merge request note")
+ end
- message = hipchat.send(:create_message, data)
+ it "should call Hipchat API for merge request comment events" do
+ data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
+ hipchat.execute(data)
- obj_attr = data[:object_attributes]
- merge_id = data[:merge_request]['iid']
- title = data[:merge_request]['title']
+ expect(WebMock).to have_requested(:post, api_url).once
- expect(message).to eq("#{user.name} commented on " \
- "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
- "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
- "<b>#{title}</b>" \
- "<pre>merge request note</pre>")
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ merge_id = data[:merge_request]['iid']
+ title = data[:merge_request]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>merge request note</pre>")
+ end
end
- it "should call Hipchat API for issue comment events" do
- data = Gitlab::NoteDataBuilder.build(issue_note, user)
- hipchat.execute(data)
+ context 'when issue comment event triggered' do
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_note) do
+ create(:note_on_issue, noteable: issue, project: project,
+ note: "issue note")
+ end
- message = hipchat.send(:create_message, data)
+ it "should call Hipchat API for issue comment events" do
+ data = Gitlab::NoteDataBuilder.build(issue_note, user)
+ hipchat.execute(data)
- obj_attr = data[:object_attributes]
- issue_id = data[:issue]['iid']
- title = data[:issue]['title']
+ message = hipchat.send(:create_message, data)
- expect(message).to eq("#{user.name} commented on " \
- "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
- "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
- "<b>#{title}</b>" \
- "<pre>issue note</pre>")
+ obj_attr = data[:object_attributes]
+ issue_id = data[:issue]['iid']
+ title = data[:issue]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>issue note</pre>")
+ end
end
- it "should call Hipchat API for snippet comment events" do
- data = Gitlab::NoteDataBuilder.build(snippet_note, user)
- hipchat.execute(data)
+ context 'when snippet comment event triggered' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:snippet_note) do
+ create(:note_on_project_snippet, noteable: snippet,
+ project: project,
+ note: "snippet note")
+ end
- expect(WebMock).to have_requested(:post, api_url).once
+ it "should call Hipchat API for snippet comment events" do
+ data = Gitlab::NoteDataBuilder.build(snippet_note, user)
+ hipchat.execute(data)
- message = hipchat.send(:create_message, data)
+ expect(WebMock).to have_requested(:post, api_url).once
- obj_attr = data[:object_attributes]
- snippet_id = data[:snippet]['id']
- title = data[:snippet]['title']
+ message = hipchat.send(:create_message, data)
- expect(message).to eq("#{user.name} commented on " \
- "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
- "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
- "<b>#{title}</b>" \
- "<pre>snippet note</pre>")
+ obj_attr = data[:object_attributes]
+ snippet_id = data[:snippet]['id']
+ title = data[:snippet]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>snippet note</pre>")
+ end
end
end
@@ -303,7 +334,7 @@ describe HipchatService, models: true do
it "should notify only broken" do
hipchat.notify_only_broken_builds = true
hipchat.execute(data)
- expect(WebMock).to_not have_requested(:post, api_url).once
+ expect(WebMock).not_to have_requested(:post, api_url).once
end
end
end
diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb
index 621c83c0cda..7fcfdf0eacd 100644
--- a/spec/models/project_services/slack_service/build_message_spec.rb
+++ b/spec/models/project_services/slack_service/build_message_spec.rb
@@ -15,7 +15,7 @@ describe SlackService::BuildMessage do
commit: {
status: status,
author_name: 'hacker',
- duration: 10,
+ duration: duration,
},
}
end
@@ -23,9 +23,10 @@ describe SlackService::BuildMessage do
context 'succeeded' do
let(:status) { 'success' }
let(:color) { 'good' }
-
+ let(:duration) { 10 }
+
it 'returns a message with information about succeeded build' do
- message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 second(s)'
+ message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 seconds'
expect(subject.pretext).to be_empty
expect(subject.fallback).to eq(message)
expect(subject.attachments).to eq([text: message, color: color])
@@ -35,9 +36,23 @@ describe SlackService::BuildMessage do
context 'failed' do
let(:status) { 'failed' }
let(:color) { 'danger' }
+ let(:duration) { 10 }
it 'returns a message with information about failed build' do
- message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 second(s)'
+ message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 seconds'
+ expect(subject.pretext).to be_empty
+ expect(subject.fallback).to eq(message)
+ expect(subject.attachments).to eq([text: message, color: color])
+ end
+ end
+
+ describe '#seconds_name' do
+ let(:status) { 'failed' }
+ let(:color) { 'danger' }
+ let(:duration) { 1 }
+
+ it 'returns seconds as singular when there is only one' do
+ message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 1 second'
expect(subject.pretext).to be_empty
expect(subject.fallback).to eq(message)
expect(subject.attachments).to eq([text: message, color: color])
diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb
index f648cbe2dee..0f8889bdf3c 100644
--- a/spec/models/project_services/slack_service/issue_message_spec.rb
+++ b/spec/models/project_services/slack_service/issue_message_spec.rb
@@ -25,7 +25,7 @@ describe SlackService::IssueMessage, models: true do
}
end
- let(:color) { '#345' }
+ let(:color) { '#C95823' }
context '#initialize' do
before do
@@ -40,10 +40,11 @@ describe SlackService::IssueMessage, models: true do
context 'open' do
it 'returns a message regarding opening of issues' do
expect(subject.pretext).to eq(
- 'Test User opened <url|issue #100> in <somewhere.com|project_name>: '\
- '*Issue title*')
+ '<somewhere.com|[project_name>] Issue opened by Test User')
expect(subject.attachments).to eq([
{
+ title: "#100 Issue title",
+ title_link: "url",
text: "issue description",
color: color,
}
@@ -56,10 +57,10 @@ describe SlackService::IssueMessage, models: true do
args[:object_attributes][:action] = 'close'
args[:object_attributes][:state] = 'closed'
end
+
it 'returns a message regarding closing of issues' do
expect(subject.pretext). to eq(
- 'Test User closed <url|issue #100> in <somewhere.com|project_name>: '\
- '*Issue title*')
+ '<somewhere.com|[project_name>] Issue <url|#100 Issue title> closed by Test User')
expect(subject.attachments).to be_empty
end
end
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
index d37590cab75..379c3e1219c 100644
--- a/spec/models/project_services/slack_service/note_message_spec.rb
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -65,7 +65,7 @@ describe SlackService::NoteMessage, models: true do
expect(message.pretext).to eq("Test User commented on " \
"<url|merge request !30> in <somewhere.com|project_name>: " \
"*merge request title*")
- expected_attachments = [
+ expected_attachments = [
{
text: "comment on a merge request",
color: color,
@@ -117,7 +117,7 @@ describe SlackService::NoteMessage, models: true do
expect(message.pretext).to eq("Test User commented on " \
"<url|snippet #5> in <somewhere.com|project_name>: " \
"*snippet title*")
- expected_attachments = [
+ expected_attachments = [
{
text: "comment on a snippet",
color: color,
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index a97b7560137..155f3e74e0d 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -142,13 +142,6 @@ describe SlackService, models: true do
let(:slack) { SlackService.new }
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id) }
- let(:issue) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:snippet) { create(:project_snippet, project: project) }
- let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
- let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") }
- let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")}
- let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") }
let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
before do
@@ -162,32 +155,61 @@ describe SlackService, models: true do
WebMock.stub_request(:post, webhook_url)
end
- it "should call Slack API for commit comment events" do
- data = Gitlab::NoteDataBuilder.build(commit_note, user)
- slack.execute(data)
+ context 'when commit comment event executed' do
+ let(:commit_note) do
+ create(:note_on_commit, author: user,
+ project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit')
+ end
- expect(WebMock).to have_requested(:post, webhook_url).once
+ it "should call Slack API for commit comment events" do
+ data = Gitlab::NoteDataBuilder.build(commit_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
end
- it "should call Slack API for merge request comment events" do
- data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
- slack.execute(data)
+ context 'when merge request comment event executed' do
+ let(:merge_request_note) do
+ create(:note_on_merge_request, project: project,
+ note: "merge request note")
+ end
- expect(WebMock).to have_requested(:post, webhook_url).once
+ it "should call Slack API for merge request comment events" do
+ data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
end
- it "should call Slack API for issue comment events" do
- data = Gitlab::NoteDataBuilder.build(issue_note, user)
- slack.execute(data)
+ context 'when issue comment event executed' do
+ let(:issue_note) do
+ create(:note_on_issue, project: project, note: "issue note")
+ end
- expect(WebMock).to have_requested(:post, webhook_url).once
+ it "should call Slack API for issue comment events" do
+ data = Gitlab::NoteDataBuilder.build(issue_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
end
- it "should call Slack API for snippet comment events" do
- data = Gitlab::NoteDataBuilder.build(snippet_note, user)
- slack.execute(data)
+ context 'when snippet comment event executed' do
+ let(:snippet_note) do
+ create(:note_on_project_snippet, project: project,
+ note: "snippet note")
+ end
- expect(WebMock).to have_requested(:post, webhook_url).once
+ it "should call Slack API for snippet comment events" do
+ data = Gitlab::NoteDataBuilder.build(snippet_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
end
end
end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index e0feb606f78..d9d7c0b0aaa 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
require 'spec_helper'
describe ProjectSnippet, models: true do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 3a8a6c2c166..338a4c3d3f0 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,42 +1,3 @@
-# == Schema Information
-#
-# 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
-# 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
-#
-
require 'spec_helper'
describe Project, models: true do
@@ -99,7 +60,7 @@ describe Project, models: true do
project2 = build(:project)
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
expect(project2).not_to be_valid
- expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
+ expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
end
end
@@ -673,11 +634,11 @@ describe Project, models: true do
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
# call. This makes testing a bit easier.
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- end
- it 'renames a repository' do
allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+ end
+ it 'renames a repository' do
ns = project.namespace_dir
expect(gitlab_shell).to receive(:mv_repository).
@@ -702,6 +663,17 @@ describe Project, models: true do
project.rename_repo
end
+
+ context 'container registry with tags' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags('tag')
+ end
+
+ subject { project.rename_repo }
+
+ it { expect{subject}.to raise_error(Exception) }
+ end
end
describe '#expire_caches_before_rename' do
@@ -811,4 +783,80 @@ describe Project, models: true do
expect(project.protected_branch?('foo')).to eq(false)
end
end
+
+ describe '#container_registry_path_with_namespace' do
+ let(:project) { create(:empty_project, path: 'PROJECT') }
+
+ subject { project.container_registry_path_with_namespace }
+
+ it { is_expected.not_to eq(project.path_with_namespace) }
+ it { is_expected.to eq(project.path_with_namespace.downcase) }
+ end
+
+ describe '#container_registry_repository' do
+ let(:project) { create(:empty_project) }
+
+ before { stub_container_registry_config(enabled: true) }
+
+ subject { project.container_registry_repository }
+
+ it { is_expected.not_to be_nil }
+ end
+
+ describe '#container_registry_repository_url' do
+ let(:project) { create(:empty_project) }
+
+ subject { project.container_registry_repository_url }
+
+ before { stub_container_registry_config(**registry_settings) }
+
+ context 'for enabled registry' do
+ let(:registry_settings) do
+ {
+ enabled: true,
+ host_port: 'example.com',
+ }
+ end
+
+ it { is_expected.not_to be_nil }
+ end
+
+ context 'for disabled registry' do
+ let(:registry_settings) do
+ {
+ enabled: false
+ }
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#has_container_registry_tags?' do
+ let(:project) { create(:empty_project) }
+
+ subject { project.has_container_registry_tags? }
+
+ context 'for enabled registry' do
+ before { stub_container_registry_config(enabled: true) }
+
+ context 'with tags' do
+ before { stub_container_registry_tags('test', 'test2') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when no tags' do
+ before { stub_container_registry_tags }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'for disabled registry' do
+ before { stub_container_registry_config(enabled: false) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 532e3f013fd..58b57bd4fef 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -16,6 +16,12 @@ describe ProjectWiki, models: true do
end
end
+ describe '#web_url' do
+ it 'returns the full web URL to the wiki' do
+ expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/wikis/home")
+ end
+ end
+
describe "#url_to_repo" do
it "returns the correct ssh url to the repo" do
expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.path_with_namespace))
@@ -38,7 +44,8 @@ describe ProjectWiki, models: true do
describe "#wiki_base_path" do
it "returns the wiki base path" do
- wiki_base_path = "/#{project.path_with_namespace}/wikis"
+ wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.path_with_namespace}/wikis"
+
expect(subject.wiki_base_path).to eq(wiki_base_path)
end
end
@@ -256,6 +263,13 @@ describe ProjectWiki, models: true do
end
end
+ describe '#hook_attrs' do
+ it 'returns a hash with values' do
+ expect(subject.hook_attrs).to be_a Hash
+ expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
+ end
+ end
+
private
def create_temp_repo(path)
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 7e956cf6779..b523834c6e9 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: protected_branches
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-# developers_can_push :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe ProtectedBranch, models: true do
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 72ecb442a36..527005b2b69 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string(255)
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'rails_helper'
RSpec.describe Release, type: :model do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 34a13f9b5c9..8c2347992f1 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -100,6 +100,12 @@ describe Repository, models: true do
expect(results.first).not_to start_with('fatal:')
end
+ it 'properly handles an unmatched parenthesis' do
+ results = repository.search_files("test(", 'master')
+
+ expect(results.first).not_to start_with('fatal:')
+ end
+
describe 'result' do
subject { results.first }
@@ -176,6 +182,15 @@ describe Repository, models: true do
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
end
+ it 'handles when HEAD points to non-existent ref' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+ rugged = double('rugged')
+ expect(rugged).to receive(:head_unborn?).and_return(true)
+ expect(repository).to receive(:rugged).and_return(rugged)
+
+ expect(repository.license_blob).to be_nil
+ end
+
it 'looks in the root_ref only' do
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown')
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false)
@@ -204,6 +219,15 @@ describe Repository, models: true do
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
end
+ it 'handles when HEAD points to non-existent ref' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+ rugged = double('rugged')
+ expect(rugged).to receive(:head_unborn?).and_return(true)
+ expect(repository).to receive(:rugged).and_return(rugged)
+
+ expect(repository.license_key).to be_nil
+ end
+
it 'returns nil when no license is detected' do
expect(repository.license_key).to be_nil
end
@@ -419,7 +443,7 @@ describe Repository, models: true do
end
it 'does nothing' do
- expect(repository.raw_repository).to_not receive(:autocrlf=).
+ expect(repository.raw_repository).not_to receive(:autocrlf=).
with(:input)
repository.update_autocrlf_option
@@ -487,7 +511,7 @@ describe Repository, models: true do
it 'does not expire the emptiness caches for a non-empty repository' do
expect(repository).to receive(:empty?).and_return(false)
- expect(repository).to_not receive(:expire_emptiness_caches)
+ expect(repository).not_to receive(:expire_emptiness_caches)
repository.expire_cache
end
@@ -650,7 +674,7 @@ describe Repository, models: true do
end
it 'does not flush caches that depend on repository data' do
- expect(repository).to_not receive(:expire_cache)
+ expect(repository).not_to receive(:expire_cache)
repository.before_delete
end
@@ -805,18 +829,6 @@ describe Repository, models: true do
end
end
- describe "#main_language" do
- it 'shows the main language of the project' do
- expect(repository.main_language).to eq("Ruby")
- end
-
- it 'returns nil when the repository is empty' do
- allow(repository).to receive(:empty?).and_return(true)
-
- expect(repository.main_language).to be_nil
- end
- end
-
describe '#before_remove_tag' do
it 'flushes the tag cache' do
expect(repository).to receive(:expire_tag_count_cache)
@@ -927,7 +939,7 @@ describe Repository, models: true do
expect(repository.avatar).to eq('logo.png')
- expect(repository).to_not receive(:blob_at_branch)
+ expect(repository).not_to receive(:blob_at_branch)
expect(repository.avatar).to eq('logo.png')
end
end
@@ -1021,7 +1033,7 @@ describe Repository, models: true do
and_return(true)
repository.cache_keys.each do |key|
- expect(repository).to_not receive(key)
+ expect(repository).not_to receive(key)
end
repository.build_cache
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 173628c08d0..8592e112c50 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -1,24 +1,3 @@
-# == 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
-# build_events :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Service, models: true do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 5077ac7b62b..789816bf2c7 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
require 'spec_helper'
describe Snippet, models: true do
@@ -103,4 +87,31 @@ describe Snippet, models: true do
expect(described_class.search_code('FOO')).to eq([snippet])
end
end
+
+ describe '#participants' do
+ let(:project) { create(:project, :public) }
+ let(:snippet) { create(:snippet, content: 'foo', project: project) }
+
+ let!(:note1) do
+ create(:note_on_project_snippet,
+ noteable: snippet,
+ project: project,
+ note: 'a')
+ end
+
+ let!(:note2) do
+ create(:note_on_project_snippet,
+ noteable: snippet,
+ project: project,
+ note: 'b')
+ end
+
+ it 'includes the snippet author' do
+ expect(snippet.participants).to include(snippet.author)
+ end
+
+ it 'includes the note authors' do
+ expect(snippet.participants).to include(note1.author, note2.author)
+ end
+ end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index d9b86b9368f..623b82c01d8 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
require 'spec_helper'
describe Todo, models: true do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8b2fb77e28e..548bec364f8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,66 +1,3 @@
-# == Schema Information
-#
-# 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)
-# 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)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# 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)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# 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)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# 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)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
-# unlock_token :string
-# otp_grace_period_started_at :datetime
-#
-
require 'spec_helper'
describe User, models: true do
@@ -130,7 +67,7 @@ describe User, models: true do
describe 'email' do
context 'when no signup domains listed' do
- before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
+ before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
@@ -138,7 +75,7 @@ describe User, models: true do
end
context 'when a signup domain is listed and subdomains are allowed' do
- before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com']) }
+ before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com']) }
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
@@ -156,7 +93,7 @@ describe User, models: true do
end
context 'when a signup domain is listed and subdomains are not allowed' do
- before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return(['example.com']) }
+ before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com']) }
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
@@ -204,6 +141,7 @@ describe User, models: true do
end
describe '#confirm' do
+ before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do
@@ -845,4 +783,23 @@ describe User, models: true do
it { is_expected.to eq([private_project]) }
end
+
+ describe '#viewable_starred_projects' do
+ let(:user) { create(:user) }
+ let(:public_project) { create(:empty_project, :public) }
+ let(:private_project) { create(:empty_project, :private) }
+ let(:private_viewable_project) { create(:empty_project, :private) }
+
+ before do
+ private_viewable_project.team << [user, Gitlab::Access::MASTER]
+
+ [public_project, private_project, private_viewable_project].each do |project|
+ user.toggle_star(project)
+ end
+ end
+
+ it 'returns only starred projects the user can view' do
+ expect(user.viewable_starred_projects).not_to include(private_project)
+ end
+ end
end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 5ead735be48..0fbc984c061 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -106,8 +106,8 @@ describe API::API, api: true do
context 'authorized user' do
let(:download_headers) do
- { 'Content-Transfer-Encoding'=>'binary',
- 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
it 'should return specific build artifacts' do
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index f3785b19362..633927c8c3e 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::CommitStatus, api: true do
+describe API::CommitStatuses, api: true do
include ApiHelpers
let!(:project) { create(:project) }
diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb
new file mode 100644
index 00000000000..aab2d8c81b9
--- /dev/null
+++ b/spec/requests/api/gitignores_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe API::Gitignores, api: true do
+ include ApiHelpers
+
+ describe 'Entity Gitignore' do
+ before { get api('/gitignores/Ruby') }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ describe 'Entity GitignoresList' do
+ before { get api('/gitignores') }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ describe 'GET /gitignores' do
+ it 'returns a list of available license templates' do
+ get api('/gitignores')
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
index 96d89e69209..02553d0f8e2 100644
--- a/spec/requests/api/group_members_spec.rb
+++ b/spec/requests/api/group_members_spec.rb
@@ -34,11 +34,11 @@ describe API::API, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(5)
- expect(json_response.find { |e| e['id']==owner.id }['access_level']).to eq(GroupMember::OWNER)
- expect(json_response.find { |e| e['id']==reporter.id }['access_level']).to eq(GroupMember::REPORTER)
- expect(json_response.find { |e| e['id']==developer.id }['access_level']).to eq(GroupMember::DEVELOPER)
- expect(json_response.find { |e| e['id']==master.id }['access_level']).to eq(GroupMember::MASTER)
- expect(json_response.find { |e| e['id']==guest.id }['access_level']).to eq(GroupMember::GUEST)
+ expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER)
+ expect(json_response.find { |e| e['id'] == reporter.id }['access_level']).to eq(GroupMember::REPORTER)
+ expect(json_response.find { |e| e['id'] == developer.id }['access_level']).to eq(GroupMember::DEVELOPER)
+ expect(json_response.find { |e| e['id'] == master.id }['access_level']).to eq(GroupMember::MASTER)
+ expect(json_response.find { |e| e['id'] == guest.id }['access_level']).to eq(GroupMember::GUEST)
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 37ddab83c30..7ecefce80d6 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -12,6 +12,7 @@ describe API::API, api: true do
let!(:group2) { create(:group, :private) }
let!(:project1) { create(:project, namespace: group1) }
let!(:project2) { create(:project, namespace: group2) }
+ let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
before do
group1.add_owner(user1)
@@ -147,9 +148,11 @@ describe API::API, api: true do
context "when authenticated as user" do
it "should return the group's projects" do
get api("/groups/#{group1.id}/projects", user1)
+
expect(response.status).to eq(200)
- expect(json_response.length).to eq(1)
- expect(json_response.first['name']).to eq(project1.name)
+ expect(json_response.length).to eq(2)
+ project_names = json_response.map { |proj| proj['name' ] }
+ expect(project_names).to match_array([project1.name, project3.name])
end
it "should not return a non existing group" do
@@ -162,6 +165,16 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+
+ it "should only return projects to which user has access" do
+ project3.team << [user3, :developer]
+
+ get api("/groups/#{group1.id}/projects", user3)
+
+ expect(response.status).to eq(200)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(project3.name)
+ end
end
context "when authenticated as admin" do
@@ -181,8 +194,10 @@ describe API::API, api: true do
context 'when using group path in URL' do
it 'should return any existing group' do
get api("/groups/#{group1.path}/projects", admin)
+
expect(response.status).to eq(200)
- expect(json_response.first['name']).to eq(project1.name)
+ project_names = json_response.map { |proj| proj['name' ] }
+ expect(project_names).to match_array([project1.name, project3.name])
end
it 'should not return a non existing group' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index f88e39cad9e..37ab9cc8cfe 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -39,6 +39,7 @@ describe API::API, api: true do
let!(:empty_milestone) do
create(:milestone, title: '2.0.0', project: project)
end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
before { project.team << [user, :reporter] }
@@ -232,8 +233,28 @@ describe API::API, api: true do
end
describe "GET /projects/:id/issues/:issue_id" do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(issue.id)
+ expect(json_response['iid']).to eq(issue.iid)
+ expect(json_response['project_id']).to eq(issue.project.id)
+ expect(json_response['title']).to eq(issue.title)
+ expect(json_response['description']).to eq(issue.description)
+ expect(json_response['state']).to eq(issue.state)
+ expect(json_response['created_at']).to be_present
+ expect(json_response['updated_at']).to be_present
+ expect(json_response['labels']).to eq(issue.label_names)
+ expect(json_response['milestone']).to be_a Hash
+ expect(json_response['assignee']).to be_a Hash
+ expect(json_response['author']).to be_a Hash
+ expect(json_response['user_notes_count']).to be(1)
+ end
+
it "should return a project issue by id" do
get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
expect(response.status).to eq(200)
expect(json_response['title']).to eq(issue.title)
expect(json_response['iid']).to eq(issue.iid)
@@ -602,6 +623,12 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+
+ it 'returns 404 if the issue is confidential' do
+ post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
+
+ expect(response.status).to eq(404)
+ end
end
describe 'DELETE :id/issues/:issue_id/subscription' do
@@ -623,5 +650,11 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+
+ it 'returns 404 if the issue is confidential' do
+ delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member)
+
+ expect(response.status).to eq(404)
+ end
end
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 6943ff9d26c..b2c7f8d9acb 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -190,4 +190,86 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
end
+
+ describe "POST /projects/:id/labels/:label_id/subscription" do
+ context "when label_id is a label title" do
+ it "should subscribe to the label" do
+ post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_truthy
+ end
+ end
+
+ context "when label_id is a label ID" do
+ it "should subscribe to the label" do
+ post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response.status).to eq(201)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_truthy
+ end
+ end
+
+ context "when user is already subscribed to label" do
+ before { label1.subscribe(user) }
+
+ it "should return 304" do
+ post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+ end
+
+ context "when label ID is not found" do
+ it "should a return 404 error" do
+ post api("/projects/#{project.id}/labels/1234/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
+ describe "DELETE /projects/:id/labels/:label_id/subscription" do
+ before { label1.subscribe(user) }
+
+ context "when label_id is a label title" do
+ it "should unsubscribe from the label" do
+ delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_falsey
+ end
+ end
+
+ context "when label_id is a label ID" do
+ it "should unsubscribe from the label" do
+ delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response["name"]).to eq(label1.title)
+ expect(json_response["subscribed"]).to be_falsey
+ end
+ end
+
+ context "when user is already unsubscribed from label" do
+ before { label1.unsubscribe(user) }
+
+ it "should return 304" do
+ delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+ end
+
+ context "when label ID is not found" do
+ it "should a return 404 error" do
+ delete api("/projects/#{project.id}/labels/1234/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb
index c17dcb222a9..3726b2f5688 100644
--- a/spec/requests/api/licenses_spec.rb
+++ b/spec/requests/api/licenses_spec.rb
@@ -57,7 +57,7 @@ describe API::Licenses, api: true do
end
it 'replaces placeholder values' do
- expect(json_response['content']).to include('Copyright (c) 2016 Anton')
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton")
end
end
@@ -70,7 +70,7 @@ describe API::Licenses, api: true do
it 'replaces placeholder values' do
expect(json_response['content']).to include('My Awesome Project')
- expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
end
end
@@ -83,7 +83,7 @@ describe API::Licenses, api: true do
it 'replaces placeholder values' do
expect(json_response['content']).to include('My Awesome Project')
- expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
end
end
@@ -96,7 +96,7 @@ describe API::Licenses, api: true do
it 'replaces placeholder values' do
expect(json_response['content']).to include('My Awesome Project')
- expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton")
end
end
@@ -108,7 +108,7 @@ describe API::Licenses, api: true do
end
it 'replaces placeholder values' do
- expect(json_response['content']).to include('Copyright 2016 Anton')
+ expect(json_response['content']).to include("Copyright #{Time.now.year} Anton")
end
end
@@ -128,7 +128,7 @@ describe API::Licenses, api: true do
it 'replaces the copyright owner placeholder with the name of the current user' do
get api('/licenses/mit', user)
- expect(json_response['content']).to include("Copyright (c) 2016 #{user.name}")
+ expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 1fa7e76894f..4b0111df149 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -113,6 +113,34 @@ describe API::API, api: true do
end
describe "GET /projects/:id/merge_requests/:merge_request_id" do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(merge_request.id)
+ expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['project_id']).to eq(merge_request.project.id)
+ expect(json_response['title']).to eq(merge_request.title)
+ expect(json_response['description']).to eq(merge_request.description)
+ expect(json_response['state']).to eq(merge_request.state)
+ expect(json_response['created_at']).to be_present
+ expect(json_response['updated_at']).to be_present
+ expect(json_response['labels']).to eq(merge_request.label_names)
+ expect(json_response['milestone']).to be_nil
+ expect(json_response['assignee']).to be_a Hash
+ expect(json_response['author']).to be_a Hash
+ expect(json_response['target_branch']).to eq(merge_request.target_branch)
+ expect(json_response['source_branch']).to eq(merge_request.source_branch)
+ expect(json_response['upvotes']).to eq(0)
+ expect(json_response['downvotes']).to eq(0)
+ expect(json_response['source_project_id']).to eq(merge_request.source_project.id)
+ expect(json_response['target_project_id']).to eq(merge_request.target_project.id)
+ expect(json_response['work_in_progress']).to be_falsy
+ expect(json_response['merge_when_build_succeeds']).to be_falsy
+ expect(json_response['merge_status']).to eq('can_be_merged')
+ expect(json_response['user_notes_count']).to be(2)
+ end
+
it "should return merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 49091fc0f49..beb29a68692 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, :public, 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) }
@@ -39,6 +39,7 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should return an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(issue_note.note)
@@ -46,20 +47,33 @@ describe API::API, api: true do
it "should return a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/12345/notes", user)
+
expect(response.status).to eq(404)
end
- context "that references a private issue" do
+ context "and current user cannot view the notes" do
it "should return an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response).to be_empty
end
+ context "and issue is confidential" do
+ before { ext_issue.update_attributes(confidential: true) }
+
+ it "returns 404" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
context "and current user can view the note" do
it "should return an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(cross_reference_note.note)
@@ -71,6 +85,7 @@ describe API::API, api: true do
context "when noteable is a Snippet" do
it "should return an array of snippet notes" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(snippet_note.note)
@@ -78,6 +93,13 @@ describe API::API, api: true do
it "should return a 404 error when snippet id not found" do
get api("/projects/#{project.id}/snippets/42/notes", user)
+
+ expect(response.status).to eq(404)
+ end
+
+ it "returns 404 when not authorized" do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user)
+
expect(response.status).to eq(404)
end
end
@@ -85,6 +107,7 @@ describe API::API, api: true do
context "when noteable is a Merge Request" do
it "should return an array of merge_requests notes" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(merge_request_note.note)
@@ -92,6 +115,13 @@ describe API::API, api: true do
it "should return a 404 error if merge request id not found" do
get api("/projects/#{project.id}/merge_requests/4444/notes", user)
+
+ expect(response.status).to eq(404)
+ end
+
+ it "returns 404 when not authorized" do
+ get api("/projects/#{project.id}/merge_requests/4444/notes", private_user)
+
expect(response.status).to eq(404)
end
end
@@ -101,24 +131,39 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should return an issue note by id" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user)
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq(issue_note.note)
end
it "should return a 404 error if issue note not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
+
expect(response.status).to eq(404)
end
- context "that references a private issue" do
+ context "and current user cannot view the note" do
it "should return a 404 error" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
+
expect(response.status).to eq(404)
end
+ context "when issue is confidential" do
+ before { issue.update_attributes(confidential: true) }
+
+ it "returns 404" do
+ get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+
context "and current user can view the note" do
it "should return an issue note by id" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq(cross_reference_note.note)
end
@@ -129,12 +174,14 @@ describe API::API, api: true do
context "when noteable is a Snippet" do
it "should return a snippet note by id" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq(snippet_note.note)
end
it "should return a 404 error if snippet note not found" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
+
expect(response.status).to eq(404)
end
end
@@ -144,6 +191,7 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should create a new issue note" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
+
expect(response.status).to eq(201)
expect(json_response['body']).to eq('hi!')
expect(json_response['author']['username']).to eq(user.username)
@@ -151,11 +199,13 @@ describe API::API, api: true do
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
+
expect(response.status).to eq(400)
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
+
expect(response.status).to eq(401)
end
@@ -164,6 +214,7 @@ describe API::API, api: true do
creation_time = 2.weeks.ago
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user),
body: 'hi!', created_at: creation_time
+
expect(response.status).to eq(201)
expect(json_response['body']).to eq('hi!')
expect(json_response['author']['username']).to eq(user.username)
@@ -176,6 +227,7 @@ describe API::API, api: true do
context "when noteable is a Snippet" do
it "should create a new snippet note" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
+
expect(response.status).to eq(201)
expect(json_response['body']).to eq('hi!')
expect(json_response['author']['username']).to eq(user.username)
@@ -183,11 +235,13 @@ describe API::API, api: true do
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
+
expect(response.status).to eq(400)
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
+
expect(response.status).to eq(401)
end
end
@@ -204,8 +258,8 @@ describe API::API, api: true do
body: 'Hi!'
end
- it 'responds with 500' do
- expect(response.status).to eq 500
+ it 'responds with resource not found error' do
+ expect(response.status).to eq 404
end
it 'does not create new note' do
@@ -227,6 +281,7 @@ describe API::API, api: true do
it 'should return modified note' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user), body: 'Hello!'
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq('Hello!')
end
@@ -234,12 +289,14 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
+
expect(response.status).to eq(404)
end
it 'should return a 400 bad request error if body not given' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user)
+
expect(response.status).to eq(400)
end
end
@@ -248,6 +305,7 @@ describe API::API, api: true do
it 'should return modified note' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/#{snippet_note.id}", user), body: 'Hello!'
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq('Hello!')
end
@@ -255,6 +313,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/12345", user), body: "Hello!"
+
expect(response.status).to eq(404)
end
end
@@ -263,6 +322,7 @@ describe API::API, api: true do
it 'should return modified note' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/#{merge_request_note.id}", user), body: 'Hello!'
+
expect(response.status).to eq(200)
expect(json_response['body']).to eq('Hello!')
end
@@ -270,6 +330,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/12345", user), body: "Hello!"
+
expect(response.status).to eq(404)
end
end
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index c112ca5e3ca..44b532b10e1 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -133,7 +133,7 @@ describe API::API, api: true do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
expect do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
- end.to_not change { ProjectMember.count }
+ end.not_to change { ProjectMember.count }
expect(response.status).to eq(200)
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 66193eac051..f167813e07d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -10,20 +10,20 @@ describe API::API, api: true do
let(:admin) { create(:admin) }
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, :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) }
let(:project3) do
create(:project,
+ :private,
name: 'second_project',
path: 'second_project',
creator_id: user.id,
namespace: user.namespace,
merge_requests_enabled: false,
issues_enabled: false, wiki_enabled: false,
- snippets_enabled: false, visibility_level: 0)
+ snippets_enabled: false)
end
let(:project_member3) do
create(:project_member,
@@ -164,21 +164,18 @@ describe API::API, api: true do
end
describe 'GET /projects/starred' do
+ let(:public_project) { create(:project, :public) }
+
before do
- admin.starred_projects << project
- admin.save!
+ project_member2
+ user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end
- it 'should return the starred projects' do
- get api('/projects/all', admin)
+ it 'should return the starred projects viewable by the user' do
+ get api('/projects/starred', user3)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
-
- expect(json_response).to satisfy do |response|
- response.one? do |entry|
- entry['name'] == project.name
- end
- end
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
end
end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 3af61d4b335..73ae8ef631c 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -184,21 +184,24 @@ describe API::Runners, api: true do
description = shared_runner.description
active = shared_runner.active
- put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active,
- tag_list: ['ruby2.1', 'pgsql', 'mysql']
+ update_runner(shared_runner.id, admin, description: "#{description}_updated",
+ active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql'],
+ run_untagged: 'false')
shared_runner.reload
expect(response.status).to eq(200)
expect(shared_runner.description).to eq("#{description}_updated")
expect(shared_runner.active).to eq(!active)
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ expect(shared_runner.run_untagged?).to be false
end
end
context 'when runner is not shared' do
it 'should update runner' do
description = specific_runner.description
- put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
expect(response.status).to eq(200)
@@ -208,10 +211,14 @@ describe API::Runners, api: true do
end
it 'should return 404 if runner does not exists' do
- put api('/runners/9999', admin), description: 'test'
+ update_runner(9999, admin, description: 'test')
expect(response.status).to eq(404)
end
+
+ def update_runner(id, user, args)
+ put api("/runners/#{id}", user), args
+ end
end
context 'authorized user' do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 3e676515488..94eebc48ec8 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -49,7 +49,7 @@ describe API::API, api: true do
it "should not create new hook without url" do
expect do
post api("/hooks", admin)
- end.to_not change { SystemHook.count }
+ end.not_to change { SystemHook.count }
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 40b24c125b5..a7690f430c4 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -20,7 +20,7 @@ describe API::API, api: true do
end
context "when authenticated" do
- #These specs are written just in case API authentication is not required anymore
+ # These specs are written just in case API authentication is not required anymore
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index dfd361a2cdd..e5124ea5ea7 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -128,6 +128,38 @@ describe Ci::API::API do
end
end
end
+
+ context 'when build has no tags' do
+ before do
+ commit = create(:ci_commit, project: project)
+ create(:ci_build, commit: commit, tags: [])
+ end
+
+ context 'when runner is allowed to pick untagged builds' do
+ before { runner.update_column(:run_untagged, true) }
+
+ it 'picks build' do
+ register_builds
+
+ expect(response).to have_http_status 201
+ end
+ end
+
+ context 'when runner is not allowed to pick untagged builds' do
+ before { runner.update_column(:run_untagged, false) }
+
+ it 'does not pick build' do
+ register_builds
+
+ expect(response).to have_http_status 404
+ end
+ end
+
+ def register_builds
+ post ci_api("/builds/register"), token: runner.token,
+ info: { platform: :darwin }
+ end
+ end
end
describe "PUT /builds/:id" do
@@ -221,13 +253,13 @@ describe Ci::API::API do
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200)
- expect(json_response["TempPath"]).to_not be_nil
+ expect(json_response["TempPath"]).not_to be_nil
end
it "using token as header" do
post authorize_url, {}, headers_with_token
expect(response.status).to eq(200)
- expect(json_response["TempPath"]).to_not be_nil
+ expect(json_response["TempPath"]).not_to be_nil
end
end
@@ -402,8 +434,8 @@ describe Ci::API::API do
context 'build has artifacts' do
let(:build) { create(:ci_build, :artifacts) }
let(:download_headers) do
- { 'Content-Transfer-Encoding'=>'binary',
- 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
it 'should download artifact' do
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index db8189ffb79..43596f07cb5 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -12,44 +12,85 @@ describe Ci::API::API do
end
describe "POST /runners/register" do
- describe "should create a runner if token provided" do
+ context 'when runner token is provided' do
before { post ci_api("/runners/register"), token: registration_token }
- it { expect(response.status).to eq(201) }
+ it 'creates runner with default values' do
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.run_untagged).to be true
+ end
end
- describe "should create a runner with description" do
- before { post ci_api("/runners/register"), token: registration_token, description: "server.hostname" }
+ context 'when runner description is provided' do
+ before do
+ post ci_api("/runners/register"), token: registration_token,
+ description: "server.hostname"
+ end
- it { expect(response.status).to eq(201) }
- it { expect(Ci::Runner.first.description).to eq("server.hostname") }
+ it 'creates runner' do
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.description).to eq("server.hostname")
+ end
end
- describe "should create a runner with tags" do
- before { post ci_api("/runners/register"), token: registration_token, tag_list: "tag1, tag2" }
+ context 'when runner tags are provided' do
+ before do
+ post ci_api("/runners/register"), token: registration_token,
+ tag_list: "tag1, tag2"
+ end
- it { expect(response.status).to eq(201) }
- it { expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) }
+ it 'creates runner' do
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"])
+ end
end
- describe "should create a runner if project token provided" do
+ context 'when option for running untagged jobs is provided' do
+ context 'when tags are provided' do
+ it 'creates runner' do
+ post ci_api("/runners/register"), token: registration_token,
+ run_untagged: false,
+ tag_list: ['tag']
+
+ expect(response).to have_http_status 201
+ expect(Ci::Runner.first.run_untagged).to be false
+ end
+ end
+
+ context 'when tags are not provided' do
+ it 'does not create runner' do
+ post ci_api("/runners/register"), token: registration_token,
+ run_untagged: false
+
+ expect(response).to have_http_status 404
+ end
+ end
+ end
+
+ context 'when project token is provided' do
let(:project) { FactoryGirl.create(:empty_project) }
before { post ci_api("/runners/register"), token: project.runners_token }
- it { expect(response.status).to eq(201) }
- it { expect(project.runners.size).to eq(1) }
+ it 'creates runner' do
+ expect(response).to have_http_status 201
+ expect(project.runners.size).to eq(1)
+ end
end
- it "should return 403 error if token is invalid" do
- post ci_api("/runners/register"), token: 'invalid'
+ context 'when token is invalid' do
+ it 'returns 403 error' do
+ post ci_api("/runners/register"), token: 'invalid'
- expect(response.status).to eq(403)
+ expect(response).to have_http_status 403
+ end
end
- it "should return 400 error if no token" do
- post ci_api("/runners/register")
+ context 'when no token provided' do
+ it 'returns 400 error' do
+ post ci_api("/runners/register")
- expect(response.status).to eq(400)
+ expect(response).to have_http_status 400
+ end
end
%w(name version revision platform architecture).each do |param|
@@ -60,7 +101,7 @@ describe Ci::API::API do
it do
post ci_api("/runners/register"), token: registration_token, info: { param => value }
- expect(response.status).to eq(201)
+ expect(response).to have_http_status 201
is_expected.to eq(value)
end
end
@@ -71,7 +112,7 @@ describe Ci::API::API do
let!(:runner) { FactoryGirl.create(:ci_runner) }
before { delete ci_api("/runners/delete"), token: runner.token }
- it { expect(response.status).to eq(200) }
+ it { expect(response).to have_http_status 200 }
it { expect(Ci::Runner.count).to eq(0) }
end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
new file mode 100644
index 00000000000..d006ff195cf
--- /dev/null
+++ b/spec/requests/jwt_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe JwtController do
+ let(:service) { double(execute: {}) }
+ let(:service_class) { double(new: service) }
+ let(:service_name) { 'test' }
+ let(:parameters) { { service: service_name } }
+
+ before { stub_const('JwtController::SERVICES', service_name => service_class) }
+
+ context 'existing service' do
+ subject! { get '/jwt/auth', parameters }
+
+ it { expect(response.status).to eq(200) }
+
+ context 'returning custom http code' do
+ let(:service) { double(execute: { http_status: 505 }) }
+
+ it { expect(response.status).to eq(505) }
+ end
+ end
+
+ context 'when using authorized request' do
+ context 'using CI token' do
+ let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) }
+ let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ context 'project with enabled CI' do
+ let(:builds_enabled) { true }
+
+ it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
+ end
+
+ context 'project with disabled CI' do
+ let(:builds_enabled) { false }
+
+ it { expect(response.status).to eq(403) }
+ end
+ end
+
+ context 'using User login' do
+ let(:user) { create(:user) }
+ let(:headers) { { authorization: credentials('user', 'password') } }
+
+ before { expect_any_instance_of(Gitlab::Auth).to receive(:find).with('user', 'password').and_return(user) }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
+ end
+
+ context 'using invalid login' do
+ let(:headers) { { authorization: credentials('invalid', 'password') } }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ it { expect(response.status).to eq(403) }
+ end
+ end
+
+ context 'unknown service' do
+ subject! { get '/jwt/auth', service: 'unknown' }
+
+ it { expect(response.status).to eq(404) }
+ end
+
+ def credentials(login, password)
+ ActionController::HttpAuthentication::Basic.encode_credentials(login, password)
+ end
+end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index cd16a8e6322..b5ed8584c8a 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -118,3 +118,10 @@ describe Admin::DashboardController, "routing" do
expect(get("/admin")).to route_to('admin/dashboard#index')
end
end
+
+# admin_health_check GET /admin/health_check(.:format) admin/health_check#show
+describe Admin::HealthCheckController, "routing" do
+ it "to #show" do
+ expect(get("/admin/health_check")).to route_to('admin/health_check#show')
+ end
+end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 1527eddfa48..de13c0db5d1 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -1,5 +1,42 @@
require 'spec_helper'
+# user GET /u/:username/
+# user_groups GET /u/:username/groups(.:format)
+# user_projects GET /u/:username/projects(.:format)
+# user_contributed_projects GET /u/:username/contributed(.:format)
+# user_snippets GET /u/:username/snippets(.:format)
+# user_calendar GET /u/:username/calendar(.:format)
+# user_calendar_activities GET /u/:username/calendar_activities(.:format)
+describe UsersController, "routing" do
+ it "to #show" do
+ expect(get("/u/User")).to route_to('users#show', username: 'User')
+ end
+
+ it "to #groups" do
+ expect(get("/u/User/groups")).to route_to('users#groups', username: 'User')
+ end
+
+ it "to #projects" do
+ expect(get("/u/User/projects")).to route_to('users#projects', username: 'User')
+ end
+
+ it "to #contributed" do
+ expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User')
+ end
+
+ it "to #snippets" do
+ expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User')
+ end
+
+ it "to #calendar" do
+ expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User')
+ end
+
+ it "to #calendar_activities" do
+ expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User')
+ end
+end
+
# search GET /search(.:format) search#show
describe SearchController, "routing" do
it "to #show" do
@@ -27,10 +64,6 @@ end
# PUT /snippets/:id(.:format) snippets#update
# DELETE /snippets/:id(.:format) snippets#destroy
describe SnippetsController, "routing" do
- it "to #user_index" do
- expect(get("/s/User")).to route_to('snippets#index', username: 'User')
- end
-
it "to #raw" do
expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end
@@ -243,3 +276,13 @@ describe "Groups", "routing" do
expect(get('/1')).to route_to('namespaces#show', id: '1')
end
end
+
+describe HealthCheckController, 'routing' do
+ it 'to #index' do
+ expect(get('/health_check')).to route_to('health_check#index')
+ end
+
+ it 'also supports passing checks in the url' do
+ expect(get('/health_check/email')).to route_to('health_check#index', checks: 'email')
+ end
+end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
new file mode 100644
index 00000000000..67777ad48bc
--- /dev/null
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -0,0 +1,242 @@
+require 'spec_helper'
+
+describe Auth::ContainerRegistryAuthenticationService, services: true do
+ let(:current_project) { nil }
+ let(:current_user) { nil }
+ let(:current_params) { {} }
+ let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
+ let(:payload) { JWT.decode(subject[:token], rsa_key).first }
+
+ subject { described_class.new(current_project, current_user, current_params).execute }
+
+ before do
+ allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
+ allow_any_instance_of(JSONWebToken::RSAToken).to receive(:key).and_return(rsa_key)
+ end
+
+ shared_examples 'a valid token' do
+ it { is_expected.to include(:token) }
+ it { expect(payload).to include('access') }
+
+ context 'a expirable' do
+ let(:expires_at) { Time.at(payload['exp']) }
+ let(:expire_delay) { 10 }
+
+ context 'for default configuration' do
+ it { expect(expires_at).not_to be_within(2.seconds).of(Time.now + expire_delay.minutes) }
+ end
+
+ context 'for changed configuration' do
+ before { stub_application_setting(container_registry_token_expire_delay: expire_delay) }
+
+ it { expect(expires_at).to be_within(2.seconds).of(Time.now + expire_delay.minutes) }
+ end
+ end
+ end
+
+ shared_examples 'a accessible' do
+ let(:access) do
+ [{
+ 'type' => 'repository',
+ 'name' => project.path_with_namespace,
+ 'actions' => actions,
+ }]
+ end
+
+ it_behaves_like 'a valid token'
+ it { expect(payload).to include('access' => access) }
+ end
+
+ shared_examples 'an inaccessible' do
+ it_behaves_like 'a valid token'
+ it { expect(payload).to include('access' => []) }
+ end
+
+ shared_examples 'a pullable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['pull'] }
+ end
+ end
+
+ shared_examples 'a pushable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['push'] }
+ end
+ end
+
+ shared_examples 'a pullable and pushable' do
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['pull', 'push'] }
+ end
+ end
+
+ shared_examples 'a forbidden' do
+ it { is_expected.to include(http_status: 403) }
+ it { is_expected.not_to include(:token) }
+ end
+
+ describe '#full_access_token' do
+ let(:project) { create(:empty_project) }
+ let(:token) { described_class.full_access_token(project.path_with_namespace) }
+
+ subject { { token: token } }
+
+ it_behaves_like 'a accessible' do
+ let(:actions) { ['*'] }
+ end
+ end
+
+ context 'user authorization' do
+ let(:project) { create(:project) }
+ let(:current_user) { create(:user) }
+
+ context 'allow to use scope-less authentication' do
+ it_behaves_like 'a valid token'
+ end
+
+ context 'allow developer to push images' do
+ before { project.team << [current_user, :developer] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a pushable'
+ end
+
+ context 'allow reporter to pull images' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'return a least of privileges' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push,pull" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'disallow guest to pull or push images' do
+ before { project.team << [current_user, :guest] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
+ end
+
+ context 'project authorization' do
+ let(:current_project) { create(:empty_project) }
+
+ context 'allow to use scope-less authentication' do
+ it_behaves_like 'a valid token'
+ end
+
+ context 'allow to pull and push images' do
+ let(:current_params) do
+ { scope: "repository:#{current_project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'a pullable and pushable' do
+ let(:project) { current_project }
+ end
+ end
+
+ context 'for other projects' do
+ context 'when pulling' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ context 'allow for public' do
+ let(:project) { create(:empty_project, :public) }
+ it_behaves_like 'a pullable'
+ end
+
+ context 'disallow for private' do
+ let(:project) { create(:empty_project, :private) }
+ it_behaves_like 'an inaccessible'
+ end
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ context 'disallow for all' do
+ let(:project) { create(:empty_project, :public) }
+ it_behaves_like 'an inaccessible'
+ end
+ end
+ end
+
+ context 'for project without container registry' do
+ let(:project) { create(:empty_project, :public, container_registry_enabled: false) }
+
+ before { project.update(container_registry_enabled: false) }
+
+ context 'disallow when pulling' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
+ end
+ end
+
+ context 'unauthorized' do
+ context 'disallow to use scope-less authentication' do
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for invalid scope' do
+ let(:current_params) do
+ { scope: 'invalid:aa:bb' }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for private project' do
+ let(:project) { create(:empty_project, :private) }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+
+ context 'for public project' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'when pulling and pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'when pushing' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a forbidden'
+ end
+ end
+ end
+end
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
index ea5dcfa068a..9ae8f31b372 100644
--- a/spec/services/create_commit_builds_service_spec.rb
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -78,7 +78,7 @@ describe CreateCommitBuildsService, services: true do
expect(commit).to be_persisted
expect(commit.builds.any?).to be false
expect(commit.status).to eq('failed')
- expect(commit.yaml_errors).to_not be_nil
+ expect(commit.yaml_errors).not_to be_nil
end
describe :ci_skip? do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index eeab540c2fd..18692f1279a 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -158,49 +158,6 @@ describe GitPushService, services: true do
end
end
- describe "Updates main language" do
- context "before push" do
- it { expect(project.main_language).to eq(nil) }
- end
-
- context "after push" do
- def execute
- execute_service(project, user, @oldrev, @newrev, ref)
- end
-
- context "to master" do
- let(:ref) { @ref }
-
- context 'when main_language is nil' do
- it 'obtains the language from the repository' do
- expect(project.repository).to receive(:main_language)
- execute
- end
-
- it 'sets the project main language' do
- execute
- expect(project.main_language).to eq("Ruby")
- end
- end
-
- context 'when main_language is already set' do
- it 'does not check the repository' do
- execute # do an initial run to simulate lang being preset
- expect(project.repository).not_to receive(:main_language)
- execute
- end
- end
- end
-
- context "to other branch" do
- let(:ref) { 'refs/heads/feature/branch' }
-
- it { expect(project.main_language).to eq(nil) }
- end
- end
- end
-
-
describe "Updates git attributes" do
context "for default branch" do
it "calls the copy attributes method for the first push to the default branch" do
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index 6aefb48a4e8..71a0b8e2a12 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -13,8 +13,8 @@ describe Groups::CreateService, services: true do
end
context "cannot create group with restricted visibility level" do
- before { allow(current_application_settings).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) }
- it { is_expected.to_not be_persisted }
+ before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) }
+ it { is_expected.not_to be_persisted }
end
end
end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index e91906d0d49..96f050bbd9b 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -15,9 +15,7 @@ describe Issues::BulkUpdateService, services: true do
describe :close_issue do
before do
- @issues = 5.times.collect do
- create(:issue, project: @project)
- end
+ @issues = create_list(:issue, 5, project: @project)
@params = {
state_event: 'close',
issues_ids: @issues.map(&:id)
@@ -36,11 +34,8 @@ describe Issues::BulkUpdateService, services: true do
end
describe :reopen_issues do
-
before do
- @issues = 5.times.collect do
- create(:closed_issue, project: @project)
- end
+ @issues = create_list(:closed_issue, 5, project: @project)
@params = {
state_event: 'reopen',
issues_ids: @issues.map(&:id)
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index ac28b6f71f9..1ee9f3aae4d 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -54,8 +54,8 @@ describe Issues::CreateService, services: true do
label_ids: [label.id] }
end
- it 'does not assign label'do
- expect(issue.labels).to_not include label
+ it 'does not assign label' do
+ expect(issue.labels).not_to include label
end
end
@@ -69,7 +69,7 @@ describe Issues::CreateService, services: true do
end
it 'does not assign milestone' do
- expect(issue.milestone).to_not eq milestone
+ expect(issue.milestone).not_to eq milestone
end
end
end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index c15e26189a5..95fe6c2400a 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -194,10 +194,10 @@ describe Issues::MoveService, services: true do
include_context 'issue move executed'
it 'rewrites uploads in description' do
- expect(new_issue.description).to_not eq description
+ expect(new_issue.description).not_to eq description
expect(new_issue.description)
.to match(/Text and #{FileUploader::MARKDOWN_PATTERN}/)
- expect(new_issue.description).to_not include uploader.secret
+ expect(new_issue.description).not_to include uploader.secret
end
end
end
@@ -231,7 +231,7 @@ describe Issues::MoveService, services: true do
context 'user is reporter in both projects' do
include_context 'user can move issue'
- it { expect { move }.to_not raise_error }
+ it { expect { move }.not_to raise_error }
end
context 'user is reporter only in new project' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 52f69306994..be19be17151 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -27,11 +27,6 @@ describe Issues::UpdateService, services: true do
end
end
- def update_issue(opts)
- @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
- @issue.reload
- end
-
context "valid params" do
before do
opts = {
@@ -39,7 +34,8 @@ describe Issues::UpdateService, services: true do
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close',
- label_ids: [label.id]
+ label_ids: [label.id],
+ confidential: true
}
perform_enqueued_jobs do
@@ -79,13 +75,25 @@ describe Issues::UpdateService, services: true do
end
it 'creates system note about title change' do
- note = find_note('Title changed')
+ note = find_note('Changed title:')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
+ end
+
+ it 'creates system note about confidentiality change' do
+ note = find_note('Made the issue confidential')
expect(note).not_to be_nil
- expect(note.note).to eq 'Title changed from **Old title** to **New title**'
+ expect(note.note).to eq 'Made the issue confidential'
end
end
+ def update_issue(opts)
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ @issue.reload
+ end
+
context 'todos' do
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
new file mode 100644
index 00000000000..f70716c9d19
--- /dev/null
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+# Write specs in this file.
+describe MergeRequests::AddTodoWhenBuildFailsService do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { create(:project) }
+ let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
+ let(:ci_commit) { create(:ci_commit_with_one_job, ref: merge_request.source_branch, project: project, sha: sha) }
+ let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user, commit_message: 'Awesome message') }
+ let(:todo_service) { TodoService.new }
+
+ let(:merge_request) do
+ create(:merge_request, merge_user: user, source_branch: 'master',
+ target_branch: 'feature', source_project: project, target_project: project,
+ state: 'opened')
+ end
+
+ before do
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(service).to receive(:todo_service).and_return(todo_service)
+ end
+
+ describe '#execute' do
+ context 'commit status with ref' do
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) }
+
+ it 'notifies the todo service' do
+ expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
+ service.execute(commit_status)
+ end
+ end
+
+ context 'commit status with non-HEAD ref' do
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) }
+
+ it 'does not notify the todo service' do
+ expect(todo_service).not_to receive(:merge_request_build_failed)
+ service.execute(commit_status)
+ end
+ end
+
+ context 'commit status without ref' do
+ let(:commit_status) { create(:generic_commit_status) }
+
+ it 'does not notify the todo service' do
+ expect(todo_service).not_to receive(:merge_request_build_failed)
+ service.execute(commit_status)
+ end
+ end
+ end
+
+ describe '#close' do
+ context 'commit status with ref' do
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) }
+
+ it 'notifies the todo service' do
+ expect(todo_service).to receive(:merge_request_build_retried).with(merge_request)
+ service.close(commit_status)
+ end
+ end
+
+ context 'commit status with non-HEAD ref' do
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch) }
+
+ it 'does not notify the todo service' do
+ expect(todo_service).not_to receive(:merge_request_build_retried)
+ service.close(commit_status)
+ end
+ end
+
+ context 'commit status without ref' do
+ let(:commit_status) { create(:generic_commit_status) }
+
+ it 'does not notify the todo service' do
+ expect(todo_service).not_to receive(:merge_request_build_retried)
+ service.close(commit_status)
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 120f4d6a669..e433f49872d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -12,7 +12,8 @@ describe MergeRequests::CreateService, services: true do
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'feature',
- target_branch: 'master'
+ target_branch: 'master',
+ force_remove_source_branch: '1'
}
end
@@ -29,6 +30,7 @@ describe MergeRequests::CreateService, services: true do
it { expect(@merge_request).to be_valid }
it { expect(@merge_request.title).to eq('Awesome merge_request') }
it { expect(@merge_request.assignee).to be_nil }
+ it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
it 'should execute hooks with default action' do
expect(service).to have_received(:execute_hooks).with(@merge_request)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index ceb3f97280e..1b0396eb686 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -38,6 +38,21 @@ describe MergeRequests::MergeService, services: true do
end
end
+ context 'remove source branch by author' do
+ let(:service) do
+ merge_request.merge_params['force_remove_source_branch'] = '1'
+ merge_request.save!
+ MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+ end
+
+ it 'removes the source branch' do
+ expect(DeleteBranchService).to receive(:new).
+ with(merge_request.source_project, merge_request.author).
+ and_call_original
+ service.execute(merge_request)
+ end
+ end
+
context "error handling" do
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 52a302e0e1a..0861d74aede 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe MergeRequests::MergeWhenBuildSucceedsService do
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
let(:mr_merge_if_green_enabled) do
create(:merge_request, merge_when_build_succeeds: true, merge_user: user,
@@ -10,11 +10,15 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
source_project: project, target_project: project, state: "opened")
end
- let(:project) { create(:project) }
let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) }
let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') }
describe "#execute" do
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project,
+ source_branch: "feature", target_branch: 'master')
+ end
+
context 'first time enabling' do
before do
allow(merge_request).to receive(:ci_commit).and_return(ci_commit)
@@ -75,7 +79,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
allow(ci_commit).to receive(:success?).and_return(true)
allow(old_build).to receive(:sha).and_return('1234abcdef')
- expect(MergeWorker).to_not receive(:perform_async)
+ expect(MergeWorker).not_to receive(:perform_async)
service.trigger(old_build)
end
end
@@ -88,7 +92,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
it "doesn't merge a requests for status on other branch" do
allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([])
- expect(MergeWorker).to_not receive(:perform_async)
+ expect(MergeWorker).not_to receive(:perform_async)
service.trigger(commit_status)
end
@@ -122,7 +126,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end
it "doesn't merge if some stages failed" do
- expect(MergeWorker).to_not receive(:perform_async)
+ expect(MergeWorker).not_to receive(:perform_async)
build.success
test.drop
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index fea8182bd30..31b93850c7c 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -27,6 +27,20 @@ describe MergeRequests::RefreshService, services: true do
target_branch: 'feature',
target_project: @project)
+ @build_failed_todo = create(:todo,
+ :build_failed,
+ user: @user,
+ project: @project,
+ target: @merge_request,
+ author: @user)
+
+ @fork_build_failed_todo = create(:todo,
+ :build_failed,
+ user: @user,
+ project: @project,
+ target: @merge_request,
+ author: @user)
+
@commits = @merge_request.commits
@oldrev = @commits.last.id
@@ -51,6 +65,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.merge_when_build_succeeds).to be_falsey}
it { expect(@fork_merge_request).to be_open }
it { expect(@fork_merge_request.notes).to be_empty }
+ it { expect(@build_failed_todo).to be_done }
+ it { expect(@fork_build_failed_todo).to be_done }
end
context 'push to origin repo target branch' do
@@ -63,6 +79,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request).to be_merged }
it { expect(@fork_merge_request).to be_merged }
it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
+ it { expect(@build_failed_todo).to be_pending }
+ it { expect(@fork_build_failed_todo).to be_pending }
end
context 'manual merge of source branch' do
@@ -82,6 +100,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.diffs.size).to be > 0 }
it { expect(@fork_merge_request).to be_merged }
it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
+ it { expect(@build_failed_todo).to be_pending }
+ it { expect(@fork_build_failed_todo).to be_pending }
end
context 'push to fork repo source branch' do
@@ -101,6 +121,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request).to be_open }
it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') }
it { expect(@fork_merge_request).to be_open }
+ it { expect(@build_failed_todo).to be_pending }
+ it { expect(@fork_build_failed_todo).to be_pending }
end
context 'push to fork repo target branch' do
@@ -113,6 +135,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request).to be_open }
it { expect(@fork_merge_request.notes).to be_empty }
it { expect(@fork_merge_request).to be_open }
+ it { expect(@build_failed_todo).to be_pending }
+ it { expect(@fork_build_failed_todo).to be_pending }
end
context 'push to origin repo target branch after fork project was removed' do
@@ -126,6 +150,8 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request).to be_merged }
it { expect(@fork_merge_request).to be_open }
it { expect(@fork_merge_request.notes).to be_empty }
+ it { expect(@build_failed_todo).to be_pending }
+ it { expect(@fork_build_failed_todo).to be_pending }
end
context 'push new branch that exists in a merge request' do
@@ -153,6 +179,8 @@ describe MergeRequests::RefreshService, services: true do
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
+ @build_failed_todo.reload
+ @fork_build_failed_todo.reload
end
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 213e8c2eb3a..d4ebe28c276 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -39,7 +39,8 @@ describe MergeRequests::UpdateService, services: true do
assignee_id: user2.id,
state_event: 'close',
label_ids: [label.id],
- target_branch: 'target'
+ target_branch: 'target',
+ force_remove_source_branch: '1'
}
end
@@ -61,6 +62,7 @@ describe MergeRequests::UpdateService, services: true do
it { expect(@merge_request.labels.count).to eq(1) }
it { expect(@merge_request.labels.first.title).to eq(label.name) }
it { expect(@merge_request.target_branch).to eq('target') }
+ it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
it 'should execute hooks with update action' do
expect(service).to have_received(:execute_hooks).
@@ -90,10 +92,10 @@ describe MergeRequests::UpdateService, services: true do
end
it 'creates system note about title change' do
- note = find_note('Title changed')
+ note = find_note('Changed title:')
expect(note).not_to be_nil
- expect(note.note).to eq 'Title changed from **Old title** to **New title**'
+ expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
end
it 'creates system note about branch change' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 4bbc4ddc3ab..cef5e0d8659 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -66,6 +66,7 @@ describe NotificationService, services: true do
should_email(@subscriber)
should_email(@watcher_and_subscriber)
should_email(@subscribed_participant)
+ should_not_email(@u_guest_watcher)
should_not_email(note.author)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -100,6 +101,7 @@ describe NotificationService, services: true do
should_email(note.noteable.author)
should_email(note.noteable.assignee)
should_email(@u_mentioned)
+ should_not_email(@u_guest_watcher)
should_not_email(@u_watcher)
should_not_email(note.author)
should_not_email(@u_participating)
@@ -160,6 +162,7 @@ describe NotificationService, services: true do
should_email(member)
end
+ should_email(@u_guest_watcher)
should_email(note.noteable.author)
should_email(note.noteable.assignee)
should_not_email(note.author)
@@ -201,6 +204,7 @@ describe NotificationService, services: true do
should_email(member)
end
+ should_email(@u_guest_watcher)
should_email(note.noteable.author)
should_not_email(note.author)
should_email(@u_mentioned)
@@ -224,6 +228,7 @@ describe NotificationService, services: true do
it do
notification.new_note(note)
+ should_email(@u_guest_watcher)
should_email(@u_committer)
should_email(@u_watcher)
should_not_email(@u_mentioned)
@@ -236,6 +241,7 @@ describe NotificationService, services: true do
note.update_attribute(:note, '@mention referenced')
notification.new_note(note)
+ should_email(@u_guest_watcher)
should_email(@u_committer)
should_email(@u_watcher)
should_email(@u_mentioned)
@@ -269,6 +275,7 @@ describe NotificationService, services: true do
should_email(issue.assignee)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_not_email(@u_mentioned)
should_not_email(@u_participating)
@@ -328,6 +335,7 @@ describe NotificationService, services: true do
should_email(issue.assignee)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
@@ -342,6 +350,7 @@ describe NotificationService, services: true do
should_email(@u_mentioned)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
@@ -356,6 +365,7 @@ describe NotificationService, services: true do
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
@@ -370,6 +380,7 @@ describe NotificationService, services: true do
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
@@ -383,6 +394,7 @@ describe NotificationService, services: true do
expect(issue.assignee).to be @u_mentioned
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(issue.assignee)
@@ -411,6 +423,7 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
should_not_email(issue.author)
should_not_email(@u_watcher)
+ should_not_email(@u_guest_watcher)
should_not_email(@u_participant_mentioned)
should_not_email(@subscriber)
should_not_email(@watcher_and_subscriber)
@@ -459,6 +472,7 @@ describe NotificationService, services: true do
should_email(issue.assignee)
should_email(issue.author)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
@@ -475,6 +489,7 @@ describe NotificationService, services: true do
should_email(issue.assignee)
should_email(issue.author)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
@@ -502,6 +517,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@watcher_and_subscriber)
should_email(@u_participant_mentioned)
+ should_email(@u_guest_watcher)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
@@ -525,6 +541,7 @@ describe NotificationService, services: true do
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
+ should_email(@u_guest_watcher)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -566,6 +583,7 @@ describe NotificationService, services: true do
should_email(merge_request.assignee)
should_email(@u_watcher)
+ should_email(@u_guest_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
@@ -584,6 +602,7 @@ describe NotificationService, services: true do
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
+ should_email(@u_guest_watcher)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -599,6 +618,7 @@ describe NotificationService, services: true do
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_email(@watcher_and_subscriber)
+ should_email(@u_guest_watcher)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -620,6 +640,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participating)
+ should_not_email(@u_guest_watcher)
should_not_email(@u_disabled)
end
end
@@ -635,6 +656,8 @@ describe NotificationService, services: true do
@u_not_mentioned = create(:user, username: 'regular', notification_level: :participating)
@u_outsider_mentioned = create(:user, username: 'outsider')
+ create_guest_watcher
+
project.team << [@u_watcher, :master]
project.team << [@u_participating, :master]
project.team << [@u_participant_mentioned, :master]
@@ -644,6 +667,13 @@ describe NotificationService, services: true do
project.team << [@u_not_mentioned, :master]
end
+ def create_guest_watcher
+ @u_guest_watcher = create(:user, username: 'guest_watching')
+ setting = @u_guest_watcher.notification_settings_for(project)
+ setting.level = :watch
+ setting.save
+ end
+
def add_users_with_subscription(project, issuable)
@subscriber = create :user
@unsubscriber = create :user
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index e43903dbd3c..fd114359467 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -64,7 +64,7 @@ describe Projects::CreateService, services: true do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
- it { expect(File.exists?(@path)).to be_truthy }
+ it { expect(File.exist?(@path)).to be_truthy }
end
context 'wiki_enabled false does not create wiki repository directory' do
@@ -74,7 +74,7 @@ describe Projects::CreateService, services: true do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
- it { expect(File.exists?(@path)).to be_falsey }
+ it { expect(File.exist?(@path)).to be_falsey }
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 1ec27077717..29341c5e57e 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -13,8 +13,8 @@ describe Projects::DestroyService, services: true do
end
it { expect(Project.all).not_to include(project) }
- it { expect(Dir.exists?(path)).to be_falsey }
- it { expect(Dir.exists?(remove_path)).to be_falsey }
+ it { expect(Dir.exist?(path)).to be_falsey }
+ it { expect(Dir.exist?(remove_path)).to be_falsey }
end
context 'Sidekiq fake' do
@@ -24,8 +24,31 @@ describe Projects::DestroyService, services: true do
end
it { expect(Project.all).not_to include(project) }
- it { expect(Dir.exists?(path)).to be_falsey }
- it { expect(Dir.exists?(remove_path)).to be_truthy }
+ it { expect(Dir.exist?(path)).to be_falsey }
+ it { expect(Dir.exist?(remove_path)).to be_truthy }
+ end
+
+ context 'container registry' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags('tag')
+ end
+
+ context 'tags deletion succeeds' do
+ it do
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
+
+ destroy_project(project, user, {})
+ end
+ end
+
+ context 'tags deletion fails' do
+ before { expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(false) }
+
+ subject { destroy_project(project, user, {}) }
+
+ it { expect{subject}.to raise_error(Projects::DestroyService::DestroyError) }
+ end
end
def destroy_project(project, user, params)
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index d1ee60a0aea..31bb7120d84 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -42,6 +42,33 @@ describe Projects::ForkService, services: true do
expect(@to_project.builds_enabled?).to be_truthy
end
end
+
+ context "when project has restricted visibility level" do
+ context "and only one visibility level is restricted" do
+ before do
+ @from_project.update_attributes(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+ end
+
+ it "creates fork with highest allowed level" do
+ forked_project = fork_project(@from_project, @to_user)
+
+ expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context "and all visibility levels are restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE])
+ end
+
+ it "creates fork with private visibility levels" do
+ forked_project = fork_project(@from_project, @to_user)
+
+ expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
+ end
end
describe :fork_to_namespace do
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 7f2dcdab960..9d90bfceb73 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -49,7 +49,7 @@ describe Projects::ImportService, services: true do
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'Failed to import the repository'
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 06017317339..d5aa115a074 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -26,6 +26,17 @@ describe Projects::TransferService, services: true do
it { expect(project.namespace).to eq(user.namespace) }
end
+ context 'disallow transfering of project with tags' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags('tag')
+ end
+
+ subject { transfer_project(project, user, group) }
+
+ it { is_expected.to be_falsey }
+ end
+
context 'namespace -> not allowed namespace' do
before do
@result = transfer_project(project, user, group)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 5fbf2ae5247..29e0a63d8ce 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -208,8 +208,10 @@ describe SystemNoteService, services: true do
end
describe '.merge_when_build_succeeds' do
- let(:ci_commit) { build :ci_commit_without_jobs }
- let(:noteable) { create :merge_request }
+ let(:ci_commit) { build(:ci_commit_without_jobs )}
+ let(:noteable) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) }
@@ -221,8 +223,10 @@ describe SystemNoteService, services: true do
end
describe '.cancel_merge_when_build_succeeds' do
- let(:ci_commit) { build :ci_commit_without_jobs }
- let(:noteable) { create :merge_request }
+ let(:ci_commit) { build(:ci_commit_without_jobs) }
+ let(:noteable) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) }
@@ -241,15 +245,19 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
expect(subject.note).
- to eq "Title changed from **Old title** to **#{noteable.title}**"
+ to eq "Changed title: **{-Old title-}** → **{+#{noteable.title}+}**"
end
end
+ end
- context 'when noteable does not respond to `title' do
- let(:noteable) { double('noteable') }
+ describe '.change_issue_confidentiality' do
+ subject { described_class.change_issue_confidentiality(noteable, project, author) }
- it 'returns nil' do
- expect(subject).to be_nil
+ context 'when noteable responds to `confidential`' do
+ it_behaves_like 'a system note'
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'Made the issue visible'
end
end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 82b7fbfa816..42147736532 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -55,6 +55,25 @@ describe TodoService, services: true do
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+
+ context 'when a private group is mentioned' do
+ let(:group) { create :group, :private }
+ let(:project) { create :project, :private, group: group }
+ let(:issue) { create :issue, author: author, project: project, description: group.to_reference }
+
+ before do
+ group.add_owner(author)
+ group.add_user(member, Gitlab::Access::DEVELOPER)
+ group.add_user(john_doe, Gitlab::Access::DEVELOPER)
+
+ service.new_issue(issue, author)
+ end
+
+ it 'creates a todo for group members' do
+ should_create_todo(user: member, target: issue)
+ should_create_todo(user: john_doe, target: issue)
+ end
+ end
end
describe '#update_issue' do
@@ -286,6 +305,25 @@ describe TodoService, services: true do
expect(second_todo.reload).to be_done
end
end
+
+ describe '#merge_request_build_failed' do
+ it 'creates a pending todo for the merge request author' do
+ service.merge_request_build_failed(mr_unassigned)
+
+ should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED)
+ end
+ end
+
+ describe '#merge_request_push' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :build_failed, user: author, project: project, target: mr_assigned, author: john_doe)
+ second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mr_assigned, author: john_doe)
+ service.merge_request_push(mr_assigned, author)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).not_to be_done
+ end
+ end
end
def should_create_todo(attributes = {})
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index e849a9633b9..a8e454eb09e 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -40,8 +40,7 @@ module FilterSpecHelper
filters = [
Banzai::Filter::AutolinkFilter,
- described_class,
- Banzai::Filter::ReferenceGathererFilter
+ described_class
]
HTML::Pipeline.new(filters, context)
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
index a3f496359b1..5ebe095743b 100644
--- a/spec/support/jira_service_helper.rb
+++ b/spec/support/jira_service_helper.rb
@@ -2,11 +2,11 @@ module JiraServiceHelper
def jira_service_settings
properties = {
- "title"=>"JIRA tracker",
- "project_url"=>"http://jira.example/issues/?jql=project=A",
- "issues_url"=>"http://jira.example/browse/JIRA-1",
- "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa",
- "api_url"=>"http://jira.example/rest/api/2"
+ "title" => "JIRA tracker",
+ "project_url" => "http://jira.example/issues/?jql=project=A",
+ "issues_url" => "http://jira.example/browse/JIRA-1",
+ "new_issue_url" => "http://jira.example/secure/CreateIssue.jspa",
+ "api_url" => "http://jira.example/rest/api/2"
}
jira_tracker.update_attributes(properties: properties, active: true)
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index cd9fdc6f18e..7a0f078c72b 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -26,11 +26,13 @@ module LoginHelpers
# Internal: Login as the specified user
#
- # user - User instance to login with
- def login_with(user)
+ # user - User instance to login with
+ # remember - Whether or not to check "Remember me" (default: false)
+ def login_with(user, remember: false)
visit new_user_session_path
fill_in "user_login", with: user.email
fill_in "user_password", with: "12345678"
+ check 'user_remember_me' if remember
click_button "Sign in"
Thread.current[:current_user] = user
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index b87cd6bbca2..7fc6d6fcc5e 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -63,8 +63,12 @@ class MarkdownFeature
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
+ def simple_milestone
+ @simple_milestone ||= create(:milestone, name: 'gfm-milestone', project: project)
+ end
+
def milestone
- @milestone ||= create(:milestone, project: project)
+ @milestone ||= create(:milestone, name: 'next goal', project: project)
end
# Cross-references -----------------------------------------------------------
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 43cb6ef43f2..e005058ba5b 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -154,7 +154,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
- expect(actual).to have_selector('a.gfm.gfm-milestone', count: 3)
+ expect(actual).to have_selector('a.gfm.gfm-milestone', count: 6)
end
end
@@ -168,6 +168,16 @@ module MarkdownMatchers
expect(actual).to have_selector('input[checked]', count: 3)
end
end
+
+ # InlineDiffFilter
+ matcher :parse_inline_diffs do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('span.idiff.addition', count: 2)
+ expect(actual).to have_selector('span.idiff.deletion', count: 2)
+ end
+ end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
diff --git a/spec/support/reference_parser_helpers.rb b/spec/support/reference_parser_helpers.rb
new file mode 100644
index 00000000000..01689194eac
--- /dev/null
+++ b/spec/support/reference_parser_helpers.rb
@@ -0,0 +1,5 @@
+module ReferenceParserHelpers
+ def empty_html_link
+ Nokogiri::HTML.fragment('<a></a>').children[0]
+ end
+end
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index eec2e681117..f73416a3d0f 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -25,6 +25,23 @@ module StubGitlabCalls
allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false)
end
+ def stub_container_registry_config(registry_settings)
+ allow(Gitlab.config.registry).to receive_messages(registry_settings)
+ allow(Auth::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token')
+ end
+
+ def stub_container_registry_tags(*tags)
+ allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_tags).and_return(
+ { "tags" => tags }
+ )
+ allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_manifest).and_return(
+ JSON.load(File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'))
+ )
+ allow_any_instance_of(ContainerRegistry::Client).to receive(:blob).and_return(
+ File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')
+ )
+ end
+
private
def gitlab_url
@@ -36,20 +53,20 @@ module StubGitlabCalls
stub_request(:post, "#{gitlab_url}api/v3/session.json").
with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}",
- headers: { 'Content-Type'=>'application/json' }).
- to_return(status: 201, body: f, headers: { 'Content-Type'=>'application/json' })
+ headers: { 'Content-Type' => 'application/json' }).
+ to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' })
end
def stub_user
f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json'))
stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz").
- with(headers: { 'Content-Type'=>'application/json' }).
- to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' })
+ with(headers: { 'Content-Type' => 'application/json' }).
+ to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token").
- with(headers: { 'Content-Type'=>'application/json' }).
- to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' })
+ with(headers: { 'Content-Type' => 'application/json' }).
+ to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
end
def stub_project_8
@@ -66,19 +83,19 @@ module StubGitlabCalls
f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json'))
stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz").
- with(headers: { 'Content-Type'=>'application/json' }).
- to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' })
+ with(headers: { 'Content-Type' => 'application/json' }).
+ to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' })
end
def stub_projects_owned
stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz").
- with(headers: { 'Content-Type'=>'application/json' }).
+ with(headers: { 'Content-Type' => 'application/json' }).
to_return(status: 200, body: "", headers: {})
end
def stub_ci_enable
stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz").
- with(headers: { 'Content-Type'=>'application/json' }).
+ with(headers: { 'Content-Type' => 'application/json' }).
to_return(status: 200, body: "", headers: {})
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 05fc4c4554f..25da0917134 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require 'rake'
describe 'gitlab:app namespace rake task' do
+ let(:enable_registry) { true }
+
before :all do
Rake.application.rake_require 'tasks/gitlab/task_helpers'
Rake.application.rake_require 'tasks/gitlab/backup'
@@ -15,13 +17,17 @@ describe 'gitlab:app namespace rake task' do
FileUtils.mkdir_p('public/uploads')
end
+ before do
+ stub_container_registry_config(enabled: enable_registry)
+ end
+
def run_rake_task(task_name)
Rake::Task[task_name].reenable
Rake.application.invoke_task task_name
end
def reenable_backup_sub_tasks
- %w{db repo uploads builds artifacts lfs}.each do |subtask|
+ %w{db repo uploads builds artifacts lfs registry}.each do |subtask|
Rake::Task["gitlab:backup:#{subtask}:create"].reenable
end
end
@@ -65,6 +71,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke)
+ expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
end
@@ -122,7 +129,7 @@ describe 'gitlab:app namespace rake task' do
it 'should set correct permissions on the tar contents' do
tar_contents, exit_status = Gitlab::Popen.popen(
- %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz}
+ %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz}
)
expect(exit_status).to eq(0)
expect(tar_contents).to match('db/')
@@ -131,16 +138,29 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz')
expect(tar_contents).to match('lfs.tar.gz')
- expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/)
+ expect(tar_contents).to match('registry.tar.gz')
+ expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
end
it 'should delete temp directories' do
temp_dirs = Dir.glob(
- File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}')
+ File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}')
)
expect(temp_dirs).to be_empty
end
+
+ context 'registry disabled' do
+ let(:enable_registry) { false }
+
+ it 'should not create registry.tar.gz' do
+ tar_contents, exit_status = Gitlab::Popen.popen(
+ %W{tar -tvf #{@backup_tar}}
+ )
+ expect(exit_status).to eq(0)
+ expect(tar_contents).not_to match('registry.tar.gz')
+ end
+ end
end # backup_create task
describe "Skipping items" do
@@ -172,7 +192,7 @@ describe 'gitlab:app namespace rake task' do
it "does not contain skipped item" do
tar_contents, _exit_status = Gitlab::Popen.popen(
- %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz}
+ %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz}
)
expect(tar_contents).to match('db/')
@@ -180,6 +200,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz')
expect(tar_contents).to match('lfs.tar.gz')
+ expect(tar_contents).to match('registry.tar.gz')
expect(tar_contents).not_to match('repositories/')
end
@@ -195,6 +216,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke
+ expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke
expect(Rake::Task['gitlab:shell:setup']).to receive :invoke
expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error
end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
new file mode 100644
index 00000000000..36d03a224e4
--- /dev/null
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require 'rake'
+
+describe 'gitlab:db namespace rake task' do
+ before :all do
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/seed_fu'
+ Rake.application.rake_require 'tasks/gitlab/db'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ before do
+ # Stub out db tasks
+ allow(Rake::Task['db:migrate']).to receive(:invoke).and_return(true)
+ allow(Rake::Task['db:schema:load']).to receive(:invoke).and_return(true)
+ allow(Rake::Task['db:seed_fu']).to receive(:invoke).and_return(true)
+ end
+
+ describe 'configure' do
+ it 'should invoke db:migrate when schema has already been loaded' do
+ allow(ActiveRecord::Base.connection).to receive(:tables).and_return(['default'])
+ expect(Rake::Task['db:migrate']).to receive(:invoke)
+ expect(Rake::Task['db:schema:load']).not_to receive(:invoke)
+ expect(Rake::Task['db:seed_fu']).not_to receive(:invoke)
+ expect { run_rake_task('gitlab:db:configure') }.not_to raise_error
+ end
+
+ it 'should invoke db:shema:load and db:seed_fu when schema is not loaded' do
+ allow(ActiveRecord::Base.connection).to receive(:tables).and_return([])
+ expect(Rake::Task['db:schema:load']).to receive(:invoke)
+ expect(Rake::Task['db:seed_fu']).to receive(:invoke)
+ expect(Rake::Task['db:migrate']).not_to receive(:invoke)
+ expect { run_rake_task('gitlab:db:configure') }.not_to raise_error
+ end
+
+ it 'should not invoke any other rake tasks during an error' do
+ allow(ActiveRecord::Base).to receive(:connection).and_raise(RuntimeError, 'error')
+ expect(Rake::Task['db:migrate']).not_to receive(:invoke)
+ expect(Rake::Task['db:schema:load']).not_to receive(:invoke)
+ expect(Rake::Task['db:seed_fu']).not_to receive(:invoke)
+ expect { run_rake_task('gitlab:db:configure') }.to raise_error(RuntimeError, 'error')
+ # unstub connection so that the database cleaner still works
+ allow(ActiveRecord::Base).to receive(:connection).and_call_original
+ end
+
+ it 'should not invoke seed after a failed schema_load' do
+ allow(ActiveRecord::Base.connection).to receive(:tables).and_return([])
+ allow(Rake::Task['db:schema:load']).to receive(:invoke).and_raise(RuntimeError, 'error')
+ expect(Rake::Task['db:schema:load']).to receive(:invoke)
+ expect(Rake::Task['db:seed_fu']).not_to receive(:invoke)
+ expect(Rake::Task['db:migrate']).not_to receive(:invoke)
+ expect { run_rake_task('gitlab:db:configure') }.to raise_error(RuntimeError, 'error')
+ end
+ end
+
+ def run_rake_task(task_name)
+ Rake::Task[task_name].reenable
+ Rake.application.invoke_task task_name
+ end
+end
diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb
index 58f45ff8610..69b2b9b6d5b 100644
--- a/spec/teaspoon_env.rb
+++ b/spec/teaspoon_env.rb
@@ -41,11 +41,11 @@ Teaspoon.configure do |config|
suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
# Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
- #suite.javascripts = []
+ # suite.javascripts = []
# You can include your own stylesheets if you want to change how Teaspoon looks.
# Note: Spec related CSS can and should be loaded using fixtures.
- #suite.stylesheets = ["teaspoon"]
+ # suite.stylesheets = ["teaspoon"]
# This suites spec helper, which can require additional support files. This file is loaded before any of your test
# files are loaded.
@@ -62,19 +62,19 @@ Teaspoon.configure do |config|
# Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a
# synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name.
- #suite.hook :fixtures, &proc{}
+ # suite.hook :fixtures, &proc{}
# Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated
- # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default,
+ # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default,
# Teaspoon expands all assets to provide more valuable stack traces that reference individual source files.
- #suite.expand_assets = true
+ # suite.expand_assets = true
end
# Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also
# be run in the default suite -- but can be focused into a more specific suite.
- #config.suite :targeted do |suite|
+ # config.suite :targeted do |suite|
# suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}"
- #end
+ # end
# CONSOLE RUNNER SPECIFIC
#
@@ -94,45 +94,45 @@ Teaspoon.configure do |config|
# PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
# Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
# Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
- #config.driver = :phantomjs
+ # config.driver = :phantomjs
# Specify additional options for the driver.
#
# PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
# Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
# Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
- #config.driver_options = nil
+ # config.driver_options = nil
# Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be
# considered a failure. This is to avoid issues that can arise where tests stall.
- #config.driver_timeout = 180
+ # config.driver_timeout = 180
# Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used.
- #config.server = nil
+ # config.server = nil
# Specify a port to run on a specific port, otherwise Teaspoon will use a random available port.
- #config.server_port = nil
+ # config.server_port = nil
# Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may
# want to lower this if you know it shouldn't take long to start.
- #config.server_timeout = 20
+ # config.server_timeout = 20
# Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have
# several suites, but in environments like CI this may not be desirable.
- #config.fail_fast = true
+ # config.fail_fast = true
# Specify the formatters to use when outputting the results.
# Note: Output files can be specified by using `"junit>/path/to/output.xml"`.
#
# Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity
- #config.formatters = [:dot]
+ # config.formatters = [:dot]
# Specify if you want color output from the formatters.
- #config.color = true
+ # config.color = true
# Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to
# remove them, but in verbose applications this may not be desirable.
- #config.suppress_log = false
+ # config.suppress_log = false
# COVERAGE REPORTS / THRESHOLD ASSERTIONS
#
@@ -149,7 +149,7 @@ Teaspoon.configure do |config|
# Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage
# on the CLI.
# Set this to "true" or the name of your coverage config.
- #config.use_coverage = nil
+ # config.use_coverage = nil
# You can have multiple coverage configs by passing a name to config.coverage.
# e.g. config.coverage :ci do |coverage|
@@ -158,21 +158,21 @@ Teaspoon.configure do |config|
# Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
#
# Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
- #coverage.reports = ["text-summary", "html"]
+ # coverage.reports = ["text-summary", "html"]
# The path that the coverage should be written to - when there's an artifact to write to disk.
# Note: Relative to `config.root`.
- #coverage.output_path = "coverage"
+ # coverage.output_path = "coverage"
# Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The
# default excludes assets from vendor, gems and support libraries.
- #coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
+ # coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
# Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any
# aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil.
- #coverage.statements = nil
- #coverage.functions = nil
- #coverage.branches = nil
- #coverage.lines = nil
+ # coverage.statements = nil
+ # coverage.functions = nil
+ # coverage.branches = nil
+ # coverage.lines = nil
end
end
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 3600c771075..439da765c2c 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -6,29 +6,66 @@ describe EmailsOnPushWorker do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ let(:recipients) { user.email }
+ let(:perform) { subject.perform(project.id, recipients, data.stringify_keys) }
subject { EmailsOnPushWorker.new }
- before do
- allow(Project).to receive(:find).and_return(project)
- end
-
describe "#perform" do
- it "sends mail" do
- subject.perform(project.id, user.email, data.stringify_keys)
+ context "when there are no errors in sending" do
+ let(:email) { ActionMailer::Base.deliveries.last }
+
+ before { perform }
- email = ActionMailer::Base.deliveries.last
- expect(email.subject).to include('Change some files')
- expect(email.to).to eq([user.email])
+ it "sends a mail with the correct subject" do
+ expect(email.subject).to include('Change some files')
+ end
+
+ it "sends the mail to the correct recipient" do
+ expect(email.to).to eq([user.email])
+ end
end
- it "gracefully handles an input SMTP error" do
- ActionMailer::Base.deliveries.clear
- allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
+ context "when there is an SMTP error" do
+ before do
+ ActionMailer::Base.deliveries.clear
+ allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
+ perform
+ end
+
+ it "gracefully handles an input SMTP error" do
+ expect(ActionMailer::Base.deliveries.count).to eq(0)
+ end
+ end
+
+ context "when there are multiple recipients" do
+ let(:recipients) do
+ 1.upto(5).map { |i| user.email.sub('@', "+#{i}@") }.join("\n")
+ end
+
+ before do
+ # This is a hack because we modify the mail object before sending, for efficency,
+ # but the TestMailer adapter just appends the objects to an array. To clone a mail
+ # object, create a new one!
+ # https://github.com/mikel/mail/issues/314#issuecomment-12750108
+ allow_any_instance_of(Mail::TestMailer).to receive(:deliver!).and_wrap_original do |original, mail|
+ original.call(Mail.new(mail.encoded))
+ end
+
+ ActionMailer::Base.deliveries.clear
+ end
- subject.perform(project.id, user.email, data.stringify_keys)
+ it "sends the mail to each of the recipients" do
+ perform
+ expect(ActionMailer::Base.deliveries.count).to eq(5)
+ expect(ActionMailer::Base.deliveries.map(&:to).flatten).to contain_exactly(*recipients.split)
+ end
- expect(ActionMailer::Base.deliveries.count).to eq(0)
+ it "only generates the mail once" do
+ expect(Notify).to receive(:repository_push_email).once.and_call_original
+ expect(Premailer::Rails::CustomizedPremailer).to receive(:new).once.and_call_original
+ perform
+ end
end
end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 94ff3457902..20d3dfb42b3 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -48,6 +48,22 @@ describe PostReceive do
PostReceive.new.perform(pwd(project), key_id, base64_changes)
end
end
+
+ context "gitlab-ci.yml" do
+ subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) }
+
+ context "creates a Ci::Commit for every change" do
+ before { stub_ci_commit_to_return_yaml_file }
+
+ it { expect{ subject }.to change{ Ci::Commit.count }.by(2) }
+ end
+
+ context "does not create a Ci::Commit" do
+ before { stub_ci_commit_yaml_file(nil) }
+
+ it { expect{ subject }.not_to change{ Ci::Commit.count } }
+ end
+ end
end
context "webhook" do
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 6739063543b..f1b1574abf4 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -6,14 +6,28 @@ describe RepositoryImportWorker do
subject { described_class.new }
describe '#perform' do
- it 'imports a project' do
- expect_any_instance_of(Projects::ImportService).to receive(:execute).
- and_return({ status: :ok })
+ context 'when the import was successful' do
+ it 'imports a project' do
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).
+ and_return({ status: :ok })
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
- expect_any_instance_of(Project).to receive(:import_finish)
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+ expect_any_instance_of(Project).to receive(:import_finish)
- subject.perform(project.id)
+ subject.perform(project.id)
+ end
+ end
+
+ context 'when the import has failed' do
+ it 'hide the credentials that were used in the import URL' do
+ error = %Q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).
+ and_return({ status: :error, message: error })
+
+ subject.perform(project.id)
+
+ expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/")
+ end
end
end
end
diff --git a/vendor/assets/javascripts/jquery.scrollTo.js b/vendor/assets/javascripts/jquery.scrollTo.js
new file mode 100755
index 00000000000..7ba17766b70
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.scrollTo.js
@@ -0,0 +1,210 @@
+/*!
+ * jQuery.scrollTo
+ * Copyright (c) 2007-2015 Ariel Flesler - aflesler<a>gmail<d>com | http://flesler.blogspot.com
+ * Licensed under MIT
+ * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
+ * @projectDescription Lightweight, cross-browser and highly customizable animated scrolling with jQuery
+ * @author Ariel Flesler
+ * @version 2.1.2
+ */
+;(function(factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define(['jquery'], factory);
+ } else if (typeof module !== 'undefined' && module.exports) {
+ // CommonJS
+ module.exports = factory(require('jquery'));
+ } else {
+ // Global
+ factory(jQuery);
+ }
+})(function($) {
+ 'use strict';
+
+ var $scrollTo = $.scrollTo = function(target, duration, settings) {
+ return $(window).scrollTo(target, duration, settings);
+ };
+
+ $scrollTo.defaults = {
+ axis:'xy',
+ duration: 0,
+ limit:true
+ };
+
+ function isWin(elem) {
+ return !elem.nodeName ||
+ $.inArray(elem.nodeName.toLowerCase(), ['iframe','#document','html','body']) !== -1;
+ }
+
+ $.fn.scrollTo = function(target, duration, settings) {
+ if (typeof duration === 'object') {
+ settings = duration;
+ duration = 0;
+ }
+ if (typeof settings === 'function') {
+ settings = { onAfter:settings };
+ }
+ if (target === 'max') {
+ target = 9e9;
+ }
+
+ settings = $.extend({}, $scrollTo.defaults, settings);
+ // Speed is still recognized for backwards compatibility
+ duration = duration || settings.duration;
+ // Make sure the settings are given right
+ var queue = settings.queue && settings.axis.length > 1;
+ if (queue) {
+ // Let's keep the overall duration
+ duration /= 2;
+ }
+ settings.offset = both(settings.offset);
+ settings.over = both(settings.over);
+
+ return this.each(function() {
+ // Null target yields nothing, just like jQuery does
+ if (target === null) return;
+
+ var win = isWin(this),
+ elem = win ? this.contentWindow || window : this,
+ $elem = $(elem),
+ targ = target,
+ attr = {},
+ toff;
+
+ switch (typeof targ) {
+ // A number will pass the regex
+ case 'number':
+ case 'string':
+ if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) {
+ targ = both(targ);
+ // We are done
+ break;
+ }
+ // Relative/Absolute selector
+ targ = win ? $(targ) : $(targ, elem);
+ /* falls through */
+ case 'object':
+ if (targ.length === 0) return;
+ // DOMElement / jQuery
+ if (targ.is || targ.style) {
+ // Get the real position of the target
+ toff = (targ = $(targ)).offset();
+ }
+ }
+
+ var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset;
+
+ $.each(settings.axis.split(''), function(i, axis) {
+ var Pos = axis === 'x' ? 'Left' : 'Top',
+ pos = Pos.toLowerCase(),
+ key = 'scroll' + Pos,
+ prev = $elem[key](),
+ max = $scrollTo.max(elem, axis);
+
+ if (toff) {// jQuery / DOMElement
+ attr[key] = toff[pos] + (win ? 0 : prev - $elem.offset()[pos]);
+
+ // If it's a dom element, reduce the margin
+ if (settings.margin) {
+ attr[key] -= parseInt(targ.css('margin'+Pos), 10) || 0;
+ attr[key] -= parseInt(targ.css('border'+Pos+'Width'), 10) || 0;
+ }
+
+ attr[key] += offset[pos] || 0;
+
+ if (settings.over[pos]) {
+ // Scroll to a fraction of its width/height
+ attr[key] += targ[axis === 'x'?'width':'height']() * settings.over[pos];
+ }
+ } else {
+ var val = targ[pos];
+ // Handle percentage values
+ attr[key] = val.slice && val.slice(-1) === '%' ?
+ parseFloat(val) / 100 * max
+ : val;
+ }
+
+ // Number or 'number'
+ if (settings.limit && /^\d+$/.test(attr[key])) {
+ // Check the limits
+ attr[key] = attr[key] <= 0 ? 0 : Math.min(attr[key], max);
+ }
+
+ // Don't waste time animating, if there's no need.
+ if (!i && settings.axis.length > 1) {
+ if (prev === attr[key]) {
+ // No animation needed
+ attr = {};
+ } else if (queue) {
+ // Intermediate animation
+ animate(settings.onAfterFirst);
+ // Don't animate this axis again in the next iteration.
+ attr = {};
+ }
+ }
+ });
+
+ animate(settings.onAfter);
+
+ function animate(callback) {
+ var opts = $.extend({}, settings, {
+ // The queue setting conflicts with animate()
+ // Force it to always be true
+ queue: true,
+ duration: duration,
+ complete: callback && function() {
+ callback.call(elem, targ, settings);
+ }
+ });
+ $elem.animate(attr, opts);
+ }
+ });
+ };
+
+ // Max scrolling position, works on quirks mode
+ // It only fails (not too badly) on IE, quirks mode.
+ $scrollTo.max = function(elem, axis) {
+ var Dim = axis === 'x' ? 'Width' : 'Height',
+ scroll = 'scroll'+Dim;
+
+ if (!isWin(elem))
+ return elem[scroll] - $(elem)[Dim.toLowerCase()]();
+
+ var size = 'client' + Dim,
+ doc = elem.ownerDocument || elem.document,
+ html = doc.documentElement,
+ body = doc.body;
+
+ return Math.max(html[scroll], body[scroll]) - Math.min(html[size], body[size]);
+ };
+
+ function both(val) {
+ return $.isFunction(val) || $.isPlainObject(val) ? val : { top:val, left:val };
+ }
+
+ // Add special hooks so that window scroll properties can be animated
+ $.Tween.propHooks.scrollLeft =
+ $.Tween.propHooks.scrollTop = {
+ get: function(t) {
+ return $(t.elem)[t.prop]();
+ },
+ set: function(t) {
+ var curr = this.get(t);
+ // If interrupt is true and user scrolled, stop animating
+ if (t.options.interrupt && t._last && t._last !== curr) {
+ return $(t.elem).stop();
+ }
+ var next = Math.round(t.now);
+ // Don't waste CPU
+ // Browsers don't render floating point scroll
+ if (curr !== next) {
+ $(t.elem)[t.prop](next);
+ t._last = this.get(t);
+ }
+ }
+ };
+
+ // AMD requirement
+ return $scrollTo;
+});
diff --git a/vendor/assets/stylesheets/animate.css b/vendor/assets/stylesheets/animate.css
deleted file mode 100644
index b6f61295392..00000000000
--- a/vendor/assets/stylesheets/animate.css
+++ /dev/null
@@ -1,11 +0,0 @@
-@charset "UTF-8";
-
-/*!
- * animate.css -http://daneden.me/animate
- * Version - 3.5.1
- * Licensed under the MIT license - http://opensource.org/licenses/MIT
- *
- * Copyright (c) 2016 Daniel Eden
- */
-
-.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-name:headShake;animation-name:headShake}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp} \ No newline at end of file
diff --git a/vendor/gitignore/Actionscript.gitignore b/vendor/gitignore/Actionscript.gitignore
new file mode 100644
index 00000000000..11e612e9853
--- /dev/null
+++ b/vendor/gitignore/Actionscript.gitignore
@@ -0,0 +1,19 @@
+# Build and Release Folders
+bin/
+bin-debug/
+bin-release/
+[Oo]bj/ # FlashDevelop obj
+[Bb]in/ # FlashDevelop bin
+
+# Other files and folders
+.settings/
+
+# Executables
+*.swf
+*.air
+*.ipa
+*.apk
+
+# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
+# should NOT be excluded as they contain compiler settings and other important
+# information for Eclipse / Flash Builder.
diff --git a/vendor/gitignore/Ada.gitignore b/vendor/gitignore/Ada.gitignore
new file mode 100644
index 00000000000..b4d703968a4
--- /dev/null
+++ b/vendor/gitignore/Ada.gitignore
@@ -0,0 +1,5 @@
+# Object file
+*.o
+
+# Ada Library Information
+*.ali
diff --git a/vendor/gitignore/Agda.gitignore b/vendor/gitignore/Agda.gitignore
new file mode 100644
index 00000000000..171a38976c1
--- /dev/null
+++ b/vendor/gitignore/Agda.gitignore
@@ -0,0 +1 @@
+*.agdai
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
new file mode 100644
index 00000000000..a8368751267
--- /dev/null
+++ b/vendor/gitignore/Android.gitignore
@@ -0,0 +1,39 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# Intellij
+*.iml
+
+# Keystore files
+*.jks
diff --git a/vendor/gitignore/AppEngine.gitignore b/vendor/gitignore/AppEngine.gitignore
new file mode 100644
index 00000000000..62273454531
--- /dev/null
+++ b/vendor/gitignore/AppEngine.gitignore
@@ -0,0 +1,2 @@
+# Google App Engine generated folder
+appengine-generated/
diff --git a/vendor/gitignore/AppceleratorTitanium.gitignore b/vendor/gitignore/AppceleratorTitanium.gitignore
new file mode 100644
index 00000000000..3abea559761
--- /dev/null
+++ b/vendor/gitignore/AppceleratorTitanium.gitignore
@@ -0,0 +1,3 @@
+# Build folder and log file
+build/
+build.log
diff --git a/vendor/gitignore/ArchLinuxPackages.gitignore b/vendor/gitignore/ArchLinuxPackages.gitignore
new file mode 100644
index 00000000000..b73905529f2
--- /dev/null
+++ b/vendor/gitignore/ArchLinuxPackages.gitignore
@@ -0,0 +1,13 @@
+*.tar
+*.tar.*
+*.jar
+*.exe
+*.msi
+*.zip
+*.tgz
+*.log
+*.log.*
+*.sig
+
+pkg/
+src/
diff --git a/vendor/gitignore/Autotools.gitignore b/vendor/gitignore/Autotools.gitignore
new file mode 100644
index 00000000000..1e9158e2a85
--- /dev/null
+++ b/vendor/gitignore/Autotools.gitignore
@@ -0,0 +1,18 @@
+# http://www.gnu.org/software/automake
+
+Makefile.in
+
+# http://www.gnu.org/software/autoconf
+
+/autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.h.in
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
new file mode 100644
index 00000000000..b8bd0267bdf
--- /dev/null
+++ b/vendor/gitignore/C++.gitignore
@@ -0,0 +1,28 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
diff --git a/vendor/gitignore/C.gitignore b/vendor/gitignore/C.gitignore
new file mode 100644
index 00000000000..f805e810e5c
--- /dev/null
+++ b/vendor/gitignore/C.gitignore
@@ -0,0 +1,33 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
diff --git a/vendor/gitignore/CFWheels.gitignore b/vendor/gitignore/CFWheels.gitignore
new file mode 100644
index 00000000000..f2fec34ff89
--- /dev/null
+++ b/vendor/gitignore/CFWheels.gitignore
@@ -0,0 +1,12 @@
+# unpacked plugin folders
+plugins/**/*
+
+# files directory where uploads go
+files
+
+# DBMigrate plugin: generated SQL
+db/sql
+
+# AssetBundler plugin: generated bundles
+javascripts/bundles
+stylesheets/bundles
diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore
new file mode 100644
index 00000000000..b558e9afa6d
--- /dev/null
+++ b/vendor/gitignore/CMake.gitignore
@@ -0,0 +1,6 @@
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Makefile
+cmake_install.cmake
+install_manifest.txt
diff --git a/vendor/gitignore/CUDA.gitignore b/vendor/gitignore/CUDA.gitignore
new file mode 100644
index 00000000000..cb385db83fe
--- /dev/null
+++ b/vendor/gitignore/CUDA.gitignore
@@ -0,0 +1,6 @@
+*.i
+*.ii
+*.gpu
+*.ptx
+*.cubin
+*.fatbin
diff --git a/vendor/gitignore/CakePHP.gitignore b/vendor/gitignore/CakePHP.gitignore
new file mode 100644
index 00000000000..c6597e4eabf
--- /dev/null
+++ b/vendor/gitignore/CakePHP.gitignore
@@ -0,0 +1,25 @@
+# CakePHP 3
+
+/vendor/*
+/config/app.php
+
+/tmp/cache/models/*
+!/tmp/cache/models/empty
+/tmp/cache/persistent/*
+!/tmp/cache/persistent/empty
+/tmp/cache/views/*
+!/tmp/cache/views/empty
+/tmp/sessions/*
+!/tmp/sessions/empty
+/tmp/tests/*
+!/tmp/tests/empty
+
+/logs/*
+!/logs/empty
+
+# CakePHP 2
+
+/app/tmp/*
+/app/Config/core.php
+/app/Config/database.php
+/vendors/*
diff --git a/vendor/gitignore/ChefCookbook.gitignore b/vendor/gitignore/ChefCookbook.gitignore
new file mode 100644
index 00000000000..5ee7b7a9a18
--- /dev/null
+++ b/vendor/gitignore/ChefCookbook.gitignore
@@ -0,0 +1,9 @@
+.vagrant
+/cookbooks
+
+# Bundler
+bin/*
+.bundle/*
+
+.kitchen/
+.kitchen.local.yml
diff --git a/vendor/gitignore/Clojure.gitignore b/vendor/gitignore/Clojure.gitignore
new file mode 120000
index 00000000000..7657a270c45
--- /dev/null
+++ b/vendor/gitignore/Clojure.gitignore
@@ -0,0 +1 @@
+Leiningen.gitignore \ No newline at end of file
diff --git a/vendor/gitignore/CodeIgniter.gitignore b/vendor/gitignore/CodeIgniter.gitignore
new file mode 100644
index 00000000000..0f77d9e1d17
--- /dev/null
+++ b/vendor/gitignore/CodeIgniter.gitignore
@@ -0,0 +1,6 @@
+*/config/development
+*/logs/log-*.php
+!*/logs/index.html
+*/cache/*
+!*/cache/index.html
+!*/cache/.htaccess
diff --git a/vendor/gitignore/CommonLisp.gitignore b/vendor/gitignore/CommonLisp.gitignore
new file mode 100644
index 00000000000..4806e580b60
--- /dev/null
+++ b/vendor/gitignore/CommonLisp.gitignore
@@ -0,0 +1,3 @@
+*.FASL
+*.fasl
+*.lisp-temp
diff --git a/vendor/gitignore/Composer.gitignore b/vendor/gitignore/Composer.gitignore
new file mode 100644
index 00000000000..c4222678424
--- /dev/null
+++ b/vendor/gitignore/Composer.gitignore
@@ -0,0 +1,6 @@
+composer.phar
+/vendor/
+
+# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock
diff --git a/vendor/gitignore/Concrete5.gitignore b/vendor/gitignore/Concrete5.gitignore
new file mode 100644
index 00000000000..1fe53611e5d
--- /dev/null
+++ b/vendor/gitignore/Concrete5.gitignore
@@ -0,0 +1,4 @@
+config/site.php
+files/cache/*
+files/tmp/*
+.htaccess
diff --git a/vendor/gitignore/Coq.gitignore b/vendor/gitignore/Coq.gitignore
new file mode 100644
index 00000000000..d3083b3a605
--- /dev/null
+++ b/vendor/gitignore/Coq.gitignore
@@ -0,0 +1,3 @@
+*.vo
+*.glob
+*.v.d
diff --git a/vendor/gitignore/CraftCMS.gitignore b/vendor/gitignore/CraftCMS.gitignore
new file mode 100644
index 00000000000..a70d4772c46
--- /dev/null
+++ b/vendor/gitignore/CraftCMS.gitignore
@@ -0,0 +1,3 @@
+# Craft Storage (cache) [http://buildwithcraft.com/help/craft-storage-gitignore]
+/craft/storage/*
+!/craft/storage/logo/* \ No newline at end of file
diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore
new file mode 100644
index 00000000000..b4433f8a512
--- /dev/null
+++ b/vendor/gitignore/D.gitignore
@@ -0,0 +1,20 @@
+# Compiled Object files
+*.o
+*.obj
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Compiled Static libraries
+*.a
+*.lib
+
+# Executables
+*.exe
+
+# DUB
+.dub
+docs.json
+__dummy.html
diff --git a/vendor/gitignore/DM.gitignore b/vendor/gitignore/DM.gitignore
new file mode 100644
index 00000000000..ba5abdab836
--- /dev/null
+++ b/vendor/gitignore/DM.gitignore
@@ -0,0 +1,5 @@
+*.dmb
+*.rsc
+*.int
+*.lk
+*.zip
diff --git a/vendor/gitignore/Dart.gitignore b/vendor/gitignore/Dart.gitignore
new file mode 100644
index 00000000000..7c280441649
--- /dev/null
+++ b/vendor/gitignore/Dart.gitignore
@@ -0,0 +1,27 @@
+# See https://www.dartlang.org/tools/private-files.html
+
+# Files and directories created by pub
+.buildlog
+.packages
+.project
+.pub/
+build/
+**/packages/
+
+# Files created by dart2js
+# (Most Dart developers will use pub build to compile Dart, use/modify these
+# rules if you intend to use dart2js directly
+# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
+# differentiate from explicit Javascript files)
+*.dart.js
+*.part.js
+*.js.deps
+*.js.map
+*.info.json
+
+# Directory created by dartdoc
+doc/api/
+
+# Don't commit pubspec lock file
+# (Library packages only! Remove pattern if developing an application package)
+pubspec.lock
diff --git a/vendor/gitignore/Delphi.gitignore b/vendor/gitignore/Delphi.gitignore
new file mode 100644
index 00000000000..19864c6bbef
--- /dev/null
+++ b/vendor/gitignore/Delphi.gitignore
@@ -0,0 +1,66 @@
+# Uncomment these types if you want even more clean repository. But be careful.
+# It can make harm to an existing project source. Read explanations below.
+#
+# Resource files are binaries containing manifest, project icon and version info.
+# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.
+#*.res
+#
+# Type library file (binary). In old Delphi versions it should be stored.
+# Since Delphi 2009 it is produced from .ridl file and can safely be ignored.
+#*.tlb
+#
+# Diagram Portfolio file. Used by the diagram editor up to Delphi 7.
+# Uncomment this if you are not using diagrams or use newer Delphi version.
+#*.ddp
+#
+# Visual LiveBindings file. Added in Delphi XE2.
+# Uncomment this if you are not using LiveBindings Designer.
+#*.vlb
+#
+# Deployment Manager configuration file for your project. Added in Delphi XE2.
+# Uncomment this if it is not mobile development and you do not use remote debug feature.
+#*.deployproj
+#
+# C++ object files produced when C/C++ Output file generation is configured.
+# Uncomment this if you are not using external objects (zlib library for example).
+#*.obj
+#
+
+# Delphi compiler-generated binaries (safe to delete)
+*.exe
+*.dll
+*.bpl
+*.bpi
+*.dcp
+*.so
+*.apk
+*.drc
+*.map
+*.dres
+*.rsm
+*.tds
+*.dcu
+*.lib
+*.a
+*.o
+*.ocx
+
+# Delphi autogenerated files (duplicated info)
+*.cfg
+*.hpp
+*Resource.rc
+
+# Delphi local files (user-specific info)
+*.local
+*.identcache
+*.projdata
+*.tvsconfig
+*.dsk
+
+# Delphi history and backups
+__history/
+__recovery/
+*.~*
+
+# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
+*.stat
diff --git a/vendor/gitignore/Drupal.gitignore b/vendor/gitignore/Drupal.gitignore
new file mode 100644
index 00000000000..0d2fe537f46
--- /dev/null
+++ b/vendor/gitignore/Drupal.gitignore
@@ -0,0 +1,36 @@
+# Ignore configuration files that may contain sensitive information.
+sites/*/*settings*.php
+
+# Ignore paths that contain generated content.
+files/
+sites/*/files
+sites/*/private
+
+# Ignore default text files
+robots.txt
+/CHANGELOG.txt
+/COPYRIGHT.txt
+/INSTALL*.txt
+/LICENSE.txt
+/MAINTAINERS.txt
+/UPGRADE.txt
+/README.txt
+sites/README.txt
+sites/all/modules/README.txt
+sites/all/themes/README.txt
+
+# Ignore everything but the "sites" folder ( for non core developer )
+.htaccess
+web.config
+authorize.php
+cron.php
+index.php
+install.php
+update.php
+xmlrpc.php
+/includes
+/misc
+/modules
+/profiles
+/scripts
+/themes
diff --git a/vendor/gitignore/EPiServer.gitignore b/vendor/gitignore/EPiServer.gitignore
new file mode 100644
index 00000000000..97037de743e
--- /dev/null
+++ b/vendor/gitignore/EPiServer.gitignore
@@ -0,0 +1,4 @@
+######################
+## EPiServer Files
+######################
+*License.config
diff --git a/vendor/gitignore/Eagle.gitignore b/vendor/gitignore/Eagle.gitignore
new file mode 100644
index 00000000000..9ced1260266
--- /dev/null
+++ b/vendor/gitignore/Eagle.gitignore
@@ -0,0 +1,44 @@
+# Ignore list for Eagle, a PCB layout tool
+
+# Backup files
+*.s#?
+*.b#?
+*.l#?
+
+# Eagle project file
+# It contains a serial number and references to the file structure
+# on your computer.
+# comment the following line if you want to have your project file included.
+eagle.epf
+
+# Autorouter files
+*.pro
+*.job
+
+# CAM files
+*.$$$
+*.cmp
+*.ly2
+*.l15
+*.sol
+*.plc
+*.stc
+*.sts
+*.crc
+*.crs
+
+*.dri
+*.drl
+*.gpi
+*.pls
+
+*.drd
+*.drd.*
+
+*.info
+
+*.eps
+
+# file locks introduced since 7.x
+*.lck
+
diff --git a/vendor/gitignore/Elisp.gitignore b/vendor/gitignore/Elisp.gitignore
new file mode 100644
index 00000000000..9b4291b7fe8
--- /dev/null
+++ b/vendor/gitignore/Elisp.gitignore
@@ -0,0 +1,5 @@
+# Compiled
+*.elc
+
+# Packaging
+.cask
diff --git a/vendor/gitignore/Elixir.gitignore b/vendor/gitignore/Elixir.gitignore
new file mode 100644
index 00000000000..755b605549d
--- /dev/null
+++ b/vendor/gitignore/Elixir.gitignore
@@ -0,0 +1,5 @@
+/_build
+/cover
+/deps
+erl_crash.dump
+*.ez
diff --git a/vendor/gitignore/Elm.gitignore b/vendor/gitignore/Elm.gitignore
new file mode 100644
index 00000000000..a594364e2c0
--- /dev/null
+++ b/vendor/gitignore/Elm.gitignore
@@ -0,0 +1,4 @@
+# elm-package generated files
+elm-stuff/
+# elm-repl generated files
+repl-temp-*
diff --git a/vendor/gitignore/Erlang.gitignore b/vendor/gitignore/Erlang.gitignore
new file mode 100644
index 00000000000..8e46d5a07f8
--- /dev/null
+++ b/vendor/gitignore/Erlang.gitignore
@@ -0,0 +1,10 @@
+.eunit
+deps
+*.o
+*.beam
+*.plt
+erl_crash.dump
+ebin
+rel/example_project
+.concrete/DEV_MODE
+.rebar
diff --git a/vendor/gitignore/ExpressionEngine.gitignore b/vendor/gitignore/ExpressionEngine.gitignore
new file mode 100644
index 00000000000..314e4df123a
--- /dev/null
+++ b/vendor/gitignore/ExpressionEngine.gitignore
@@ -0,0 +1,19 @@
+.DS_Store
+
+# Images
+images/avatars/
+images/captchas/
+images/smileys/
+images/member_photos/
+images/signature_attachments/
+images/pm_attachments/
+
+# For security do not publish the following files
+system/expressionengine/config/database.php
+system/expressionengine/config/config.php
+
+# Caches
+sized/
+thumbs/
+_thumbs/
+*/expressionengine/cache/*
diff --git a/vendor/gitignore/ExtJs.gitignore b/vendor/gitignore/ExtJs.gitignore
new file mode 100644
index 00000000000..5ffc21546ec
--- /dev/null
+++ b/vendor/gitignore/ExtJs.gitignore
@@ -0,0 +1,4 @@
+.architect
+bootstrap.json
+build/
+ext/
diff --git a/vendor/gitignore/Fancy.gitignore b/vendor/gitignore/Fancy.gitignore
new file mode 100644
index 00000000000..70d6e631e55
--- /dev/null
+++ b/vendor/gitignore/Fancy.gitignore
@@ -0,0 +1,2 @@
+*.rbc
+*.fyc
diff --git a/vendor/gitignore/Finale.gitignore b/vendor/gitignore/Finale.gitignore
new file mode 100644
index 00000000000..7ef08e0c343
--- /dev/null
+++ b/vendor/gitignore/Finale.gitignore
@@ -0,0 +1,13 @@
+*.bak
+*.db
+*.avi
+*.pdf
+*.ps
+*.mid
+*.midi
+*.mp3
+*.aif
+*.wav
+# Some versions of Finale have a bug and randomly save extra copies of
+# the music source as "<Filename> copy.mus"
+*copy.mus
diff --git a/vendor/gitignore/ForceDotCom.gitignore b/vendor/gitignore/ForceDotCom.gitignore
new file mode 100644
index 00000000000..3933cd4dd50
--- /dev/null
+++ b/vendor/gitignore/ForceDotCom.gitignore
@@ -0,0 +1,4 @@
+.project
+.settings
+salesforce.schema
+Referenced Packages
diff --git a/vendor/gitignore/Fortran.gitignore b/vendor/gitignore/Fortran.gitignore
new file mode 120000
index 00000000000..5daba98a3e6
--- /dev/null
+++ b/vendor/gitignore/Fortran.gitignore
@@ -0,0 +1 @@
+C++.gitignore \ No newline at end of file
diff --git a/vendor/gitignore/FuelPHP.gitignore b/vendor/gitignore/FuelPHP.gitignore
new file mode 100644
index 00000000000..d69f71f4338
--- /dev/null
+++ b/vendor/gitignore/FuelPHP.gitignore
@@ -0,0 +1,21 @@
+# the composer package lock file and install directory
+# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# /composer.lock
+/fuel/vendor
+
+# the fuelphp document
+/docs/
+
+# you may install these packages with `oil package`.
+# http://fuelphp.com/docs/packages/oil/package.html
+# /fuel/packages/auth/
+# /fuel/packages/email/
+# /fuel/packages/oil/
+# /fuel/packages/orm/
+# /fuel/packages/parser/
+
+# dynamically generated files
+/fuel/app/logs/*/*/*
+/fuel/app/cache/*/*
+/fuel/app/config/crypt.php
diff --git a/vendor/gitignore/GWT.gitignore b/vendor/gitignore/GWT.gitignore
new file mode 100644
index 00000000000..07704e54bbc
--- /dev/null
+++ b/vendor/gitignore/GWT.gitignore
@@ -0,0 +1,28 @@
+*.class
+
+# Package Files #
+*.jar
+*.war
+
+# gwt caches and compiled units #
+war/gwt_bree/
+gwt-unitCache/
+
+# boilerplate generated classes #
+.apt_generated/
+
+# more caches and things from deploy #
+war/WEB-INF/deploy/
+war/WEB-INF/classes/
+
+#compilation logs
+.gwt/
+
+#caching for already compiled files
+gwt-unitCache/
+
+#gwt junit compilation files
+www-test/
+
+#old GWT (1.5) created this dir
+.gwt-tmp/
diff --git a/vendor/gitignore/Gcov.gitignore b/vendor/gitignore/Gcov.gitignore
new file mode 100644
index 00000000000..a6451430e17
--- /dev/null
+++ b/vendor/gitignore/Gcov.gitignore
@@ -0,0 +1,5 @@
+# gcc coverage testing tool files
+
+*.gcno
+*.gcda
+*.gcov
diff --git a/vendor/gitignore/GitBook.gitignore b/vendor/gitignore/GitBook.gitignore
new file mode 100644
index 00000000000..4cb12d8db77
--- /dev/null
+++ b/vendor/gitignore/GitBook.gitignore
@@ -0,0 +1,16 @@
+# Node rules:
+## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+## Dependency directory
+## Commenting this out is preferred by some people, see
+## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
+node_modules
+
+# Book build output
+_book
+
+# eBook build output
+*.epub
+*.mobi
+*.pdf
diff --git a/vendor/gitignore/Global/Anjuta.gitignore b/vendor/gitignore/Global/Anjuta.gitignore
new file mode 100644
index 00000000000..20dd42c53e6
--- /dev/null
+++ b/vendor/gitignore/Global/Anjuta.gitignore
@@ -0,0 +1,3 @@
+# Local configuration folder and symbol database
+/.anjuta/
+/.anjuta_sym_db.db
diff --git a/vendor/gitignore/Global/Archives.gitignore b/vendor/gitignore/Global/Archives.gitignore
new file mode 100644
index 00000000000..e9eda68baf2
--- /dev/null
+++ b/vendor/gitignore/Global/Archives.gitignore
@@ -0,0 +1,27 @@
+# It's better to unpack these files and commit the raw source because
+# git has its own built in compression methods.
+*.7z
+*.jar
+*.rar
+*.zip
+*.gz
+*.bzip
+*.bz2
+*.xz
+*.lzma
+*.cab
+
+#packing-only formats
+*.iso
+*.tar
+
+#package management formats
+*.dmg
+*.xpi
+*.gem
+*.egg
+*.deb
+*.rpm
+*.msi
+*.msm
+*.msp
diff --git a/vendor/gitignore/Global/BricxCC.gitignore b/vendor/gitignore/Global/BricxCC.gitignore
new file mode 100644
index 00000000000..c1d16a46c98
--- /dev/null
+++ b/vendor/gitignore/Global/BricxCC.gitignore
@@ -0,0 +1,4 @@
+# Bricx Command Center IDE
+# http://bricxcc.sourceforge.net
+*.bak
+*.sym
diff --git a/vendor/gitignore/Global/CVS.gitignore b/vendor/gitignore/Global/CVS.gitignore
new file mode 100644
index 00000000000..1695352e146
--- /dev/null
+++ b/vendor/gitignore/Global/CVS.gitignore
@@ -0,0 +1,4 @@
+/CVS/*
+**/CVS/*
+.cvsignore
+*/.cvsignore
diff --git a/vendor/gitignore/Global/Calabash.gitignore b/vendor/gitignore/Global/Calabash.gitignore
new file mode 100644
index 00000000000..8a75b329dcd
--- /dev/null
+++ b/vendor/gitignore/Global/Calabash.gitignore
@@ -0,0 +1,10 @@
+# Calabash / Cucumber
+rerun/
+reports/
+screenshots/
+screenshot*.png
+test-servers/
+
+# bundler
+.bundle
+vendor
diff --git a/vendor/gitignore/Global/Cloud9.gitignore b/vendor/gitignore/Global/Cloud9.gitignore
new file mode 100644
index 00000000000..3f4384df508
--- /dev/null
+++ b/vendor/gitignore/Global/Cloud9.gitignore
@@ -0,0 +1,3 @@
+# Cloud9 IDE - http://c9.io
+.c9revisions
+.c9
diff --git a/vendor/gitignore/Global/CodeKit.gitignore b/vendor/gitignore/Global/CodeKit.gitignore
new file mode 100644
index 00000000000..bd9e67fcca2
--- /dev/null
+++ b/vendor/gitignore/Global/CodeKit.gitignore
@@ -0,0 +1,3 @@
+# General CodeKit files to ignore
+config.codekit
+/min
diff --git a/vendor/gitignore/Global/DartEditor.gitignore b/vendor/gitignore/Global/DartEditor.gitignore
new file mode 100644
index 00000000000..948920b420e
--- /dev/null
+++ b/vendor/gitignore/Global/DartEditor.gitignore
@@ -0,0 +1,2 @@
+.project
+.buildlog
diff --git a/vendor/gitignore/Global/Dreamweaver.gitignore b/vendor/gitignore/Global/Dreamweaver.gitignore
new file mode 100644
index 00000000000..0621a3d53b5
--- /dev/null
+++ b/vendor/gitignore/Global/Dreamweaver.gitignore
@@ -0,0 +1,7 @@
+# DW Dreamweaver added files
+_notes
+_compareTemp
+configs/
+dwsync.xml
+dw_php_codehinting.config
+*.mno
diff --git a/vendor/gitignore/Global/Dropbox.gitignore b/vendor/gitignore/Global/Dropbox.gitignore
new file mode 100644
index 00000000000..40f4a469d25
--- /dev/null
+++ b/vendor/gitignore/Global/Dropbox.gitignore
@@ -0,0 +1,4 @@
+# Dropbox settings and caches
+.dropbox
+.dropbox.attr
+.dropbox.cache
diff --git a/vendor/gitignore/Global/Eclipse.gitignore b/vendor/gitignore/Global/Eclipse.gitignore
new file mode 100644
index 00000000000..31c9fb31167
--- /dev/null
+++ b/vendor/gitignore/Global/Eclipse.gitignore
@@ -0,0 +1,51 @@
+
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# Eclipse Core
+.project
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
diff --git a/vendor/gitignore/Global/EiffelStudio.gitignore b/vendor/gitignore/Global/EiffelStudio.gitignore
new file mode 100644
index 00000000000..f41b4f70216
--- /dev/null
+++ b/vendor/gitignore/Global/EiffelStudio.gitignore
@@ -0,0 +1,2 @@
+# The compilation directory
+EIFGENs
diff --git a/vendor/gitignore/Global/Emacs.gitignore b/vendor/gitignore/Global/Emacs.gitignore
new file mode 100644
index 00000000000..0c96c9ad060
--- /dev/null
+++ b/vendor/gitignore/Global/Emacs.gitignore
@@ -0,0 +1,42 @@
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile \ No newline at end of file
diff --git a/vendor/gitignore/Global/Ensime.gitignore b/vendor/gitignore/Global/Ensime.gitignore
new file mode 100644
index 00000000000..f2daebb9f4b
--- /dev/null
+++ b/vendor/gitignore/Global/Ensime.gitignore
@@ -0,0 +1,4 @@
+# Ensime specific
+.ensime
+.ensime_cache/
+.ensime_lucene/
diff --git a/vendor/gitignore/Global/Espresso.gitignore b/vendor/gitignore/Global/Espresso.gitignore
new file mode 100644
index 00000000000..1234530b5b3
--- /dev/null
+++ b/vendor/gitignore/Global/Espresso.gitignore
@@ -0,0 +1 @@
+*.esproj
diff --git a/vendor/gitignore/Global/FlexBuilder.gitignore b/vendor/gitignore/Global/FlexBuilder.gitignore
new file mode 100644
index 00000000000..bbbfb91d9eb
--- /dev/null
+++ b/vendor/gitignore/Global/FlexBuilder.gitignore
@@ -0,0 +1,3 @@
+bin/
+bin-debug/
+bin-release/
diff --git a/vendor/gitignore/Global/GPG.gitignore b/vendor/gitignore/Global/GPG.gitignore
new file mode 100644
index 00000000000..7740a01538c
--- /dev/null
+++ b/vendor/gitignore/Global/GPG.gitignore
@@ -0,0 +1,2 @@
+secring.*
+
diff --git a/vendor/gitignore/Global/IPythonNotebook.gitignore b/vendor/gitignore/Global/IPythonNotebook.gitignore
new file mode 100644
index 00000000000..27c13510bf5
--- /dev/null
+++ b/vendor/gitignore/Global/IPythonNotebook.gitignore
@@ -0,0 +1,2 @@
+# Temporary data
+.ipynb_checkpoints/
diff --git a/vendor/gitignore/Global/JDeveloper.gitignore b/vendor/gitignore/Global/JDeveloper.gitignore
new file mode 100644
index 00000000000..5bba6f37733
--- /dev/null
+++ b/vendor/gitignore/Global/JDeveloper.gitignore
@@ -0,0 +1,13 @@
+# default application storage directory used by the IDE Performance Cache feature
+.data/
+
+# used for ADF styles caching
+temp/
+
+# default output directories
+classes/
+deploy/
+javadoc/
+
+# lock file, a part of Oracle Credential Store Framework
+cwallet.sso.lck \ No newline at end of file
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
new file mode 100644
index 00000000000..ea83a5eb620
--- /dev/null
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -0,0 +1,44 @@
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/dictionaries
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
+
+# Sensitive or high-churn files:
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
diff --git a/vendor/gitignore/Global/KDevelop4.gitignore b/vendor/gitignore/Global/KDevelop4.gitignore
new file mode 100644
index 00000000000..7ac57b1add4
--- /dev/null
+++ b/vendor/gitignore/Global/KDevelop4.gitignore
@@ -0,0 +1,2 @@
+*.kdev4
+.kdev4/
diff --git a/vendor/gitignore/Global/Kate.gitignore b/vendor/gitignore/Global/Kate.gitignore
new file mode 100644
index 00000000000..7ff06ce5390
--- /dev/null
+++ b/vendor/gitignore/Global/Kate.gitignore
@@ -0,0 +1,3 @@
+# Swap Files #
+.*.kate-swp
+.swp.*
diff --git a/vendor/gitignore/Global/Lazarus.gitignore b/vendor/gitignore/Global/Lazarus.gitignore
new file mode 100644
index 00000000000..b32943f1c6e
--- /dev/null
+++ b/vendor/gitignore/Global/Lazarus.gitignore
@@ -0,0 +1,30 @@
+# Lazarus compiler-generated binaries (safe to delete)
+*.exe
+*.dll
+*.so
+*.dylib
+*.lrs
+*.res
+*.compiled
+*.dbg
+*.ppu
+*.o
+*.or
+*.a
+
+# Lazarus autogenerated files (duplicated info)
+*.rst
+*.rsj
+*.lrt
+
+# Lazarus local files (user-specific info)
+*.lps
+
+# Lazarus backups and unit output folders.
+# These can be changed by user in Lazarus/project options.
+backup/
+*.bak
+lib/
+
+# Application bundle for Mac OS
+*.app/
diff --git a/vendor/gitignore/Global/LibreOffice.gitignore b/vendor/gitignore/Global/LibreOffice.gitignore
new file mode 100644
index 00000000000..586beac91d3
--- /dev/null
+++ b/vendor/gitignore/Global/LibreOffice.gitignore
@@ -0,0 +1,2 @@
+# LibreOffice locks
+.~lock.*#
diff --git a/vendor/gitignore/Global/Linux.gitignore b/vendor/gitignore/Global/Linux.gitignore
new file mode 100644
index 00000000000..cc9586893b6
--- /dev/null
+++ b/vendor/gitignore/Global/Linux.gitignore
@@ -0,0 +1,10 @@
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
diff --git a/vendor/gitignore/Global/LyX.gitignore b/vendor/gitignore/Global/LyX.gitignore
new file mode 100644
index 00000000000..8efe0195cf3
--- /dev/null
+++ b/vendor/gitignore/Global/LyX.gitignore
@@ -0,0 +1,4 @@
+# Ignore LyX backup and autosave files
+# http://www.lyx.org/
+*.lyx~
+*.lyx#
diff --git a/vendor/gitignore/Global/Matlab.gitignore b/vendor/gitignore/Global/Matlab.gitignore
new file mode 100644
index 00000000000..32a5ad4c777
--- /dev/null
+++ b/vendor/gitignore/Global/Matlab.gitignore
@@ -0,0 +1,19 @@
+##---------------------------------------------------
+## Remove autosaves generated by the Matlab editor
+## We have git for backups!
+##---------------------------------------------------
+
+# Windows default autosave extension
+*.asv
+
+# OSX / *nix default autosave extension
+*.m~
+
+# Compiled MEX binaries (all platforms)
+*.mex*
+
+# Simulink Code Generation
+slprj/
+
+# Session info
+octave-workspace
diff --git a/vendor/gitignore/Global/Mercurial.gitignore b/vendor/gitignore/Global/Mercurial.gitignore
new file mode 100644
index 00000000000..e65d1137988
--- /dev/null
+++ b/vendor/gitignore/Global/Mercurial.gitignore
@@ -0,0 +1,6 @@
+.hg/
+.hgignore
+.hgsigs
+.hgsub
+.hgsubstate
+.hgtags
diff --git a/vendor/gitignore/Global/MicrosoftOffice.gitignore b/vendor/gitignore/Global/MicrosoftOffice.gitignore
new file mode 100644
index 00000000000..cb891745660
--- /dev/null
+++ b/vendor/gitignore/Global/MicrosoftOffice.gitignore
@@ -0,0 +1,16 @@
+*.tmp
+
+# Word temporary
+~$*.doc*
+
+# Excel temporary
+~$*.xls*
+
+# Excel Backup File
+*.xlk
+
+# PowerPoint temporary
+~$*.ppt*
+
+# Visio autosave temporary files
+*.~vsdx
diff --git a/vendor/gitignore/Global/ModelSim.gitignore b/vendor/gitignore/Global/ModelSim.gitignore
new file mode 100644
index 00000000000..46592b86430
--- /dev/null
+++ b/vendor/gitignore/Global/ModelSim.gitignore
@@ -0,0 +1,23 @@
+# ignore ModelSim generated files and directories (temp files and so on)
+[_@]*
+
+# ignore compilation output of ModelSim
+*.mti
+*.dat
+*.dbs
+*.psm
+*.bak
+*.cmp
+*.jpg
+*.html
+*.bsf
+
+# ignore simulation output of ModelSim
+wlf*
+*.wlf
+*.vstf
+*.ucdb
+cov*/
+transcript*
+sc_dpiheader.h
+vsim.dbg
diff --git a/vendor/gitignore/Global/Momentics.gitignore b/vendor/gitignore/Global/Momentics.gitignore
new file mode 100644
index 00000000000..b14db2d8645
--- /dev/null
+++ b/vendor/gitignore/Global/Momentics.gitignore
@@ -0,0 +1,8 @@
+# Built files
+x86/
+arm/
+arm-p/
+translations/*.qm
+
+# IDE settings
+.settings/
diff --git a/vendor/gitignore/Global/MonoDevelop.gitignore b/vendor/gitignore/Global/MonoDevelop.gitignore
new file mode 100644
index 00000000000..ef38d06b08f
--- /dev/null
+++ b/vendor/gitignore/Global/MonoDevelop.gitignore
@@ -0,0 +1,8 @@
+#User Specific
+*.userprefs
+*.usertasks
+
+#Mono Project Files
+*.pidb
+*.resources
+test-results/
diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore
new file mode 100644
index 00000000000..520d91ff584
--- /dev/null
+++ b/vendor/gitignore/Global/NetBeans.gitignore
@@ -0,0 +1,7 @@
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+nbactions.xml
+.nb-gradle/
diff --git a/vendor/gitignore/Global/Ninja.gitignore b/vendor/gitignore/Global/Ninja.gitignore
new file mode 100644
index 00000000000..50e58f24cc9
--- /dev/null
+++ b/vendor/gitignore/Global/Ninja.gitignore
@@ -0,0 +1,2 @@
+.ninja_deps
+.ninja_log
diff --git a/vendor/gitignore/Global/NotepadPP.gitignore b/vendor/gitignore/Global/NotepadPP.gitignore
new file mode 100644
index 00000000000..8fbda83a2c9
--- /dev/null
+++ b/vendor/gitignore/Global/NotepadPP.gitignore
@@ -0,0 +1,2 @@
+# Notepad++ backups #
+*.bak
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore
new file mode 100644
index 00000000000..660b31353e8
--- /dev/null
+++ b/vendor/gitignore/Global/OSX.gitignore
@@ -0,0 +1,24 @@
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
diff --git a/vendor/gitignore/Global/Otto.gitignore b/vendor/gitignore/Global/Otto.gitignore
new file mode 100644
index 00000000000..5aa263f9db0
--- /dev/null
+++ b/vendor/gitignore/Global/Otto.gitignore
@@ -0,0 +1 @@
+.otto/
diff --git a/vendor/gitignore/Global/Redcar.gitignore b/vendor/gitignore/Global/Redcar.gitignore
new file mode 100644
index 00000000000..b4a9d1d68e3
--- /dev/null
+++ b/vendor/gitignore/Global/Redcar.gitignore
@@ -0,0 +1 @@
+.redcar
diff --git a/vendor/gitignore/Global/Redis.gitignore b/vendor/gitignore/Global/Redis.gitignore
new file mode 100644
index 00000000000..57c1c230f92
--- /dev/null
+++ b/vendor/gitignore/Global/Redis.gitignore
@@ -0,0 +1,3 @@
+# Ignore redis binary dump (dump.rdb) files
+
+*.rdb
diff --git a/vendor/gitignore/Global/SBT.gitignore b/vendor/gitignore/Global/SBT.gitignore
new file mode 100644
index 00000000000..970d897c75c
--- /dev/null
+++ b/vendor/gitignore/Global/SBT.gitignore
@@ -0,0 +1,9 @@
+# Simple Build Tool
+# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
+
+target/
+lib_managed/
+src_managed/
+project/boot/
+.history
+.cache
diff --git a/vendor/gitignore/Global/SVN.gitignore b/vendor/gitignore/Global/SVN.gitignore
new file mode 100644
index 00000000000..1b53ace613f
--- /dev/null
+++ b/vendor/gitignore/Global/SVN.gitignore
@@ -0,0 +1 @@
+.svn/
diff --git a/vendor/gitignore/Global/SlickEdit.gitignore b/vendor/gitignore/Global/SlickEdit.gitignore
new file mode 100644
index 00000000000..f30b8da457c
--- /dev/null
+++ b/vendor/gitignore/Global/SlickEdit.gitignore
@@ -0,0 +1,11 @@
+# SlickEdit workspace and project files are ignored by default because
+# typically they are considered to be developer-specific and not part of a
+# project.
+*.vpw
+*.vpj
+
+# SlickEdit workspace history and tag files always contain user-specific
+# data so they should not be stored in a repository.
+*.vpwhistu
+*.vpwhist
+*.vtg
diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore
new file mode 100644
index 00000000000..1d4e6137591
--- /dev/null
+++ b/vendor/gitignore/Global/SublimeText.gitignore
@@ -0,0 +1,14 @@
+# cache files for sublime text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# workspace files are user-specific
+*.sublime-workspace
+
+# project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using SublimeText
+# *.sublime-project
+
+# sftp configuration file
+sftp-config.json
diff --git a/vendor/gitignore/Global/SynopsysVCS.gitignore b/vendor/gitignore/Global/SynopsysVCS.gitignore
new file mode 100644
index 00000000000..eed2432fb78
--- /dev/null
+++ b/vendor/gitignore/Global/SynopsysVCS.gitignore
@@ -0,0 +1,36 @@
+# Waveform formats
+*.vcd
+*.vpd
+*.evcd
+*.fsdb
+
+# Default name of the simulation executable. A different name can be
+# specified with this switch (the associated daidir database name is
+# also taken from here): -o <path>/<filename>
+simv
+
+# Generated for Verilog and VHDL top configs
+simv.daidir/
+simv.db.dir/
+
+# Infrastructure necessary to co-simulate SystemC models with
+# Verilog/VHDL models. An alternate directory may be specified with this
+# switch: -Mdir=<directory_path>
+csrc/
+
+# Log file - the following switch allows to specify the file that will be
+# used to write all messages from simulation: -l <filename>
+*.log
+
+# Coverage results (generated with urg) and database location. The
+# following switch can also be used: urg -dir <coverage_directory>.vdb
+simv.vdb/
+urgReport/
+
+# DVE and UCLI related files.
+DVEfiles/
+ucli.key
+
+# When the design is elaborated for DirectC, the following file is created
+# with declarations for C/C++ functions.
+vc_hdrs.h
diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore
new file mode 100644
index 00000000000..c0318165a27
--- /dev/null
+++ b/vendor/gitignore/Global/Tags.gitignore
@@ -0,0 +1,16 @@
+# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+
diff --git a/vendor/gitignore/Global/TextMate.gitignore b/vendor/gitignore/Global/TextMate.gitignore
new file mode 100644
index 00000000000..41e8d07a940
--- /dev/null
+++ b/vendor/gitignore/Global/TextMate.gitignore
@@ -0,0 +1,3 @@
+*.tmproj
+*.tmproject
+tmtags
diff --git a/vendor/gitignore/Global/TortoiseGit.gitignore b/vendor/gitignore/Global/TortoiseGit.gitignore
new file mode 100644
index 00000000000..db89590a629
--- /dev/null
+++ b/vendor/gitignore/Global/TortoiseGit.gitignore
@@ -0,0 +1,2 @@
+# Project-level settings
+/.tgitconfig
diff --git a/vendor/gitignore/Global/Vagrant.gitignore b/vendor/gitignore/Global/Vagrant.gitignore
new file mode 100644
index 00000000000..a977916f658
--- /dev/null
+++ b/vendor/gitignore/Global/Vagrant.gitignore
@@ -0,0 +1 @@
+.vagrant/
diff --git a/vendor/gitignore/Global/Vim.gitignore b/vendor/gitignore/Global/Vim.gitignore
new file mode 100644
index 00000000000..bdc04a0b529
--- /dev/null
+++ b/vendor/gitignore/Global/Vim.gitignore
@@ -0,0 +1,10 @@
+# swap
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+# session
+Session.vim
+# temporary
+.netrwhist
+*~
+# auto-generated tag files
+tags
diff --git a/vendor/gitignore/Global/VirtualEnv.gitignore b/vendor/gitignore/Global/VirtualEnv.gitignore
new file mode 100644
index 00000000000..b2c22f2af7f
--- /dev/null
+++ b/vendor/gitignore/Global/VirtualEnv.gitignore
@@ -0,0 +1,12 @@
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+.Python
+[Bb]in
+[Ii]nclude
+[Ll]ib
+[Ll]ib64
+[Ll]ocal
+[Ss]cripts
+pyvenv.cfg
+.venv
+pip-selfcheck.json
diff --git a/vendor/gitignore/Global/VisualStudioCode.gitignore b/vendor/gitignore/Global/VisualStudioCode.gitignore
new file mode 100644
index 00000000000..faa18382a3c
--- /dev/null
+++ b/vendor/gitignore/Global/VisualStudioCode.gitignore
@@ -0,0 +1,2 @@
+.vscode
+
diff --git a/vendor/gitignore/Global/WebMethods.gitignore b/vendor/gitignore/Global/WebMethods.gitignore
new file mode 100644
index 00000000000..b383c25ca3c
--- /dev/null
+++ b/vendor/gitignore/Global/WebMethods.gitignore
@@ -0,0 +1,14 @@
+**/IntegrationServer/datastore/
+**/IntegrationServer/db/
+**/IntegrationServer/DocumentStore/
+**/IntegrationServer/lib/
+**/IntegrationServer/logs/
+**/IntegrationServer/replicate/
+**/IntegrationServer/sdk/
+**/IntegrationServer/support/
+**/IntegrationServer/update/
+**/IntegrationServer/userFtpRoot/
+**/IntegrationServer/web/
+**/IntegrationServer/WmRepository4/
+**/IntegrationServer/XAStore/
+**/IntegrationServer/packages/Wm*/
diff --git a/vendor/gitignore/Global/Windows.gitignore b/vendor/gitignore/Global/Windows.gitignore
new file mode 100644
index 00000000000..a0d31452b0e
--- /dev/null
+++ b/vendor/gitignore/Global/Windows.gitignore
@@ -0,0 +1,18 @@
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
diff --git a/vendor/gitignore/Global/Xcode.gitignore b/vendor/gitignore/Global/Xcode.gitignore
new file mode 100644
index 00000000000..37de8bb4793
--- /dev/null
+++ b/vendor/gitignore/Global/Xcode.gitignore
@@ -0,0 +1,23 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
diff --git a/vendor/gitignore/Global/XilinxISE.gitignore b/vendor/gitignore/Global/XilinxISE.gitignore
new file mode 100644
index 00000000000..4475f843da9
--- /dev/null
+++ b/vendor/gitignore/Global/XilinxISE.gitignore
@@ -0,0 +1,67 @@
+# intermediate build files
+*.bgn
+*.bit
+*.bld
+*.cmd_log
+*.drc
+*.ll
+*.lso
+*.msd
+*.msk
+*.ncd
+*.ngc
+*.ngd
+*.ngr
+*.pad
+*.par
+*.pcf
+*.prj
+*.ptwx
+*.rbb
+*.rbd
+*.stx
+*.syr
+*.twr
+*.twx
+*.unroutes
+*.ut
+*.xpi
+*.xst
+*_bitgen.xwbt
+*_envsettings.html
+*_map.map
+*_map.mrp
+*_map.ngm
+*_map.xrpt
+*_ngdbuild.xrpt
+*_pad.csv
+*_pad.txt
+*_par.xrpt
+*_summary.html
+*_summary.xml
+*_usage.xml
+*_xst.xrpt
+
+# iMPACT generated files
+_impactbatch.log
+impact.xsl
+impact_impact.xwbt
+ise_impact.cmd
+webtalk_impact.xml
+
+# Core Generator generated files
+xaw2verilog.log
+
+# project-wide generated files
+*.gise
+par_usage_statistics.html
+usage_statistics_webtalk.html
+webtalk.log
+webtalk_pn.xml
+
+# generated folders
+iseconfig/
+xlnx_auto_0_xdb/
+xst/
+_ngo/
+_xmsgs/
diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore
new file mode 100644
index 00000000000..daf913b1b34
--- /dev/null
+++ b/vendor/gitignore/Go.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/gitignore/Gradle.gitignore b/vendor/gitignore/Gradle.gitignore
new file mode 100644
index 00000000000..77617a15c38
--- /dev/null
+++ b/vendor/gitignore/Gradle.gitignore
@@ -0,0 +1,14 @@
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
diff --git a/vendor/gitignore/Grails.gitignore b/vendor/gitignore/Grails.gitignore
new file mode 100644
index 00000000000..9185f14c37c
--- /dev/null
+++ b/vendor/gitignore/Grails.gitignore
@@ -0,0 +1,33 @@
+# .gitignore for Grails 1.2 and 1.3
+# Although this should work for most versions of grails, it is
+# suggested that you use the "grails integrate-with --git" command
+# to generate your .gitignore file.
+
+# web application files
+/web-app/WEB-INF/classes
+
+# default HSQL database files for production mode
+/prodDb.*
+
+# general HSQL database files
+*Db.properties
+*Db.script
+
+# logs
+/stacktrace.log
+/test/reports
+/logs
+
+# project release file
+/*.war
+
+# plugin release files
+/*.zip
+/plugin.xml
+
+# older plugin install locations
+/plugins
+/web-app/plugins
+
+# "temporary" build files
+/target
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
new file mode 100644
index 00000000000..096abdd90b3
--- /dev/null
+++ b/vendor/gitignore/Haskell.gitignore
@@ -0,0 +1,18 @@
+dist
+dist-*
+cabal-dev
+*.o
+*.hi
+*.chi
+*.chs.h
+*.dyn_o
+*.dyn_hi
+.hpc
+.hsenv
+.cabal-sandbox/
+cabal.sandbox.config
+*.prof
+*.aux
+*.hp
+*.eventlog
+.stack-work/
diff --git a/vendor/gitignore/IGORPro.gitignore b/vendor/gitignore/IGORPro.gitignore
new file mode 100644
index 00000000000..c62be650036
--- /dev/null
+++ b/vendor/gitignore/IGORPro.gitignore
@@ -0,0 +1,5 @@
+# Avoid including Experiment files: they can be created and edited locally to test the ipf files
+*.pxp
+*.pxt
+*.uxp
+*.uxt
diff --git a/vendor/gitignore/Idris.gitignore b/vendor/gitignore/Idris.gitignore
new file mode 100644
index 00000000000..c28bc7cc675
--- /dev/null
+++ b/vendor/gitignore/Idris.gitignore
@@ -0,0 +1,2 @@
+*.ibc
+*.o
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
new file mode 100644
index 00000000000..32858aad3c3
--- /dev/null
+++ b/vendor/gitignore/Java.gitignore
@@ -0,0 +1,12 @@
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
diff --git a/vendor/gitignore/Jboss.gitignore b/vendor/gitignore/Jboss.gitignore
new file mode 100644
index 00000000000..75d1731ed97
--- /dev/null
+++ b/vendor/gitignore/Jboss.gitignore
@@ -0,0 +1,19 @@
+jboss/server/all/deploy/project.ext
+jboss/server/default/deploy/project.ext
+jboss/server/minimal/deploy/project.ext
+jboss/server/all/log/*.log
+jboss/server/all/tmp/**/*
+jboss/server/all/data/**/*
+jboss/server/all/work/**/*
+jboss/server/default/log/*.log
+jboss/server/default/tmp/**/*
+jboss/server/default/data/**/*
+jboss/server/default/work/**/*
+jboss/server/minimal/log/*.log
+jboss/server/minimal/tmp/**/*
+jboss/server/minimal/data/**/*
+jboss/server/minimal/work/**/*
+
+# deployed package files #
+
+*.DEPLOYED
diff --git a/vendor/gitignore/Jekyll.gitignore b/vendor/gitignore/Jekyll.gitignore
new file mode 100644
index 00000000000..5c91b60c063
--- /dev/null
+++ b/vendor/gitignore/Jekyll.gitignore
@@ -0,0 +1,3 @@
+_site/
+.sass-cache/
+.jekyll-metadata
diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore
new file mode 100644
index 00000000000..0d7a0de298f
--- /dev/null
+++ b/vendor/gitignore/Joomla.gitignore
@@ -0,0 +1,546 @@
+/.gitignore
+/.htaccess
+/administrator/cache/*
+/administrator/components/com_admin/*
+/administrator/components/com_ajax/*
+/administrator/components/com_tags/*
+/administrator/components/com_banners/*
+/administrator/components/com_cache/*
+/administrator/components/com_postinstall/*
+/administrator/components/com_joomlaupdate/*
+/administrator/components/com_contenthistory/*
+/administrator/components/com_categories/*
+/administrator/components/com_checkin/*
+/administrator/components/com_config/*
+/administrator/components/com_contact/*
+/administrator/components/com_content/*
+/administrator/components/com_cpanel/*
+/administrator/components/com_finder/*
+/administrator/components/com_installer/*
+/administrator/components/com_languages/*
+/administrator/components/com_login/*
+/administrator/components/com_media/*
+/administrator/components/com_menus/*
+/administrator/components/com_messages/*
+/administrator/components/com_modules/*
+/administrator/components/com_newsfeeds/*
+/administrator/components/com_plugins/*
+/administrator/components/com_redirect/*
+/administrator/components/com_search/*
+/administrator/components/com_templates/*
+/administrator/components/com_users/*
+/administrator/components/com_weblinks/*
+/administrator/components/index.html
+/administrator/help/*
+/administrator/includes/*
+/administrator/language/en-GB/en-GB.com_ajax.ini
+/administrator/language/en-GB/en-GB.com_ajax.sys.ini
+/administrator/language/en-GB/en-GB.com_contenthistory.ini
+/administrator/language/en-GB/en-GB.com_contenthistory.sys.ini
+/administrator/language/en-GB/en-GB.com_joomlaupdate.ini
+/administrator/language/en-GB/en-GB.com_joomlaupdate.sys.ini
+/administrator/language/en-GB/en-GB.com_postinstall.ini
+/administrator/language/en-GB/en-GB.com_postinstall.sys.ini
+/administrator/language/en-GB/en-GB.com_sitemapjen.sys.ini
+/administrator/language/en-GB/en-GB.com_tags.ini
+/administrator/language/en-GB/en-GB.com_tags.sys.ini
+/administrator/language/en-GB/en-GB.mod_stats_admin.ini
+/administrator/language/en-GB/en-GB.mod_stats_admin.sys.ini
+/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini
+/administrator/language/en-GB/en-GB.plg_authentication_cookie.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_contact.ini
+/administrator/language/en-GB/en-GB.plg_content_contact.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_finder.ini
+/administrator/language/en-GB/en-GB.plg_content_finder.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_categories.ini
+/administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_contacts.ini
+/administrator/language/en-GB/en-GB.plg_finder_contacts.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_content.ini
+/administrator/language/en-GB/en-GB.plg_finder_content.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.ini
+/administrator/language/en-GB/en-GB.plg_finder_tags.ini
+/administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini
+/administrator/language/en-GB/en-GB.plg_finder_weblinks.ini
+/administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini
+/administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini
+/administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini
+/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini
+/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_tags.ini
+/administrator/language/en-GB/en-GB.plg_search_tags.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_languagecode.ini
+/administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini
+/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini
+/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini
+/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini
+/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.sys.ini
+/administrator/language/en-GB/en-GB.tpl_isis.ini
+/administrator/language/en-GB/en-GB.tpl_isis.sys.ini
+/administrator/language/en-GB/install.xml
+/administrator/language/en-GB/en-GB.com_admin.ini
+/administrator/language/en-GB/en-GB.com_admin.sys.ini
+/administrator/language/en-GB/en-GB.com_banners.ini
+/administrator/language/en-GB/en-GB.com_banners.sys.ini
+/administrator/language/en-GB/en-GB.com_cache.ini
+/administrator/language/en-GB/en-GB.com_cache.sys.ini
+/administrator/language/en-GB/en-GB.com_categories.ini
+/administrator/language/en-GB/en-GB.com_categories.sys.ini
+/administrator/language/en-GB/en-GB.com_checkin.ini
+/administrator/language/en-GB/en-GB.com_checkin.sys.ini
+/administrator/language/en-GB/en-GB.com_config.ini
+/administrator/language/en-GB/en-GB.com_config.sys.ini
+/administrator/language/en-GB/en-GB.com_contact.ini
+/administrator/language/en-GB/en-GB.com_contact.sys.ini
+/administrator/language/en-GB/en-GB.com_content.ini
+/administrator/language/en-GB/en-GB.com_content.sys.ini
+/administrator/language/en-GB/en-GB.com_cpanel.ini
+/administrator/language/en-GB/en-GB.com_cpanel.sys.ini
+/administrator/language/en-GB/en-GB.com_finder.ini
+/administrator/language/en-GB/en-GB.com_finder.sys.ini
+/administrator/language/en-GB/en-GB.com_installer.ini
+/administrator/language/en-GB/en-GB.com_installer.sys.ini
+/administrator/language/en-GB/en-GB.com_languages.ini
+/administrator/language/en-GB/en-GB.com_languages.sys.ini
+/administrator/language/en-GB/en-GB.com_login.ini
+/administrator/language/en-GB/en-GB.com_login.sys.ini
+/administrator/language/en-GB/en-GB.com_mailto.sys.ini
+/administrator/language/en-GB/en-GB.com_media.ini
+/administrator/language/en-GB/en-GB.com_media.sys.ini
+/administrator/language/en-GB/en-GB.com_menus.ini
+/administrator/language/en-GB/en-GB.com_menus.sys.ini
+/administrator/language/en-GB/en-GB.com_messages.ini
+/administrator/language/en-GB/en-GB.com_messages.sys.ini
+/administrator/language/en-GB/en-GB.com_modules.ini
+/administrator/language/en-GB/en-GB.com_modules.sys.ini
+/administrator/language/en-GB/en-GB.com_newsfeeds.ini
+/administrator/language/en-GB/en-GB.com_newsfeeds.sys.ini
+/administrator/language/en-GB/en-GB.com_plugins.ini
+/administrator/language/en-GB/en-GB.com_plugins.sys.ini
+/administrator/language/en-GB/en-GB.com_redirect.ini
+/administrator/language/en-GB/en-GB.com_redirect.sys.ini
+/administrator/language/en-GB/en-GB.com_search.ini
+/administrator/language/en-GB/en-GB.com_search.sys.ini
+/administrator/language/en-GB/en-GB.com_templates.ini
+/administrator/language/en-GB/en-GB.com_templates.sys.ini
+/administrator/language/en-GB/en-GB.com_users.ini
+/administrator/language/en-GB/en-GB.com_users.sys.ini
+/administrator/language/en-GB/en-GB.com_weblinks.ini
+/administrator/language/en-GB/en-GB.com_weblinks.sys.ini
+/administrator/language/en-GB/en-GB.com_wrapper.ini
+/administrator/language/en-GB/en-GB.com_wrapper.sys.ini
+/administrator/language/en-GB/en-GB.ini
+/administrator/language/en-GB/en-GB.lib_joomla.ini
+/administrator/language/en-GB/en-GB.localise.php
+/administrator/language/en-GB/en-GB.mod_custom.ini
+/administrator/language/en-GB/en-GB.mod_custom.sys.ini
+/administrator/language/en-GB/en-GB.mod_feed.ini
+/administrator/language/en-GB/en-GB.mod_feed.sys.ini
+/administrator/language/en-GB/en-GB.mod_latest.ini
+/administrator/language/en-GB/en-GB.mod_latest.sys.ini
+/administrator/language/en-GB/en-GB.mod_logged.ini
+/administrator/language/en-GB/en-GB.mod_logged.sys.ini
+/administrator/language/en-GB/en-GB.mod_login.ini
+/administrator/language/en-GB/en-GB.mod_login.sys.ini
+/administrator/language/en-GB/en-GB.mod_menu.ini
+/administrator/language/en-GB/en-GB.mod_menu.sys.ini
+/administrator/language/en-GB/en-GB.mod_multilangstatus.ini
+/administrator/language/en-GB/en-GB.mod_multilangstatus.sys.ini
+/administrator/language/en-GB/en-GB.mod_online.ini
+/administrator/language/en-GB/en-GB.mod_online.sys.ini
+/administrator/language/en-GB/en-GB.mod_popular.ini
+/administrator/language/en-GB/en-GB.mod_popular.sys.ini
+/administrator/language/en-GB/en-GB.mod_quickicon.ini
+/administrator/language/en-GB/en-GB.mod_quickicon.sys.ini
+/administrator/language/en-GB/en-GB.mod_status.ini
+/administrator/language/en-GB/en-GB.mod_status.sys.ini
+/administrator/language/en-GB/en-GB.mod_submenu.ini
+/administrator/language/en-GB/en-GB.mod_submenu.sys.ini
+/administrator/language/en-GB/en-GB.mod_title.ini
+/administrator/language/en-GB/en-GB.mod_title.sys.ini
+/administrator/language/en-GB/en-GB.mod_toolbar.ini
+/administrator/language/en-GB/en-GB.mod_toolbar.sys.ini
+/administrator/language/en-GB/en-GB.mod_unread.ini
+/administrator/language/en-GB/en-GB.mod_unread.sys.ini
+/administrator/language/en-GB/en-GB.mod_version.ini
+/administrator/language/en-GB/en-GB.mod_version.sys.ini
+/administrator/language/en-GB/en-GB.plg_authentication_example.ini
+/administrator/language/en-GB/en-GB.plg_authentication_example.sys.ini
+/administrator/language/en-GB/en-GB.plg_authentication_gmail.ini
+/administrator/language/en-GB/en-GB.plg_authentication_gmail.sys.ini
+/administrator/language/en-GB/en-GB.plg_authentication_joomla.ini
+/administrator/language/en-GB/en-GB.plg_authentication_joomla.sys.ini
+/administrator/language/en-GB/en-GB.plg_authentication_ldap.ini
+/administrator/language/en-GB/en-GB.plg_authentication_ldap.sys.ini
+/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini
+/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_emailcloak.ini
+/administrator/language/en-GB/en-GB.plg_content_emailcloak.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_geshi.ini
+/administrator/language/en-GB/en-GB.plg_content_geshi.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_joomla.ini
+/administrator/language/en-GB/en-GB.plg_content_joomla.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_loadmodule.ini
+/administrator/language/en-GB/en-GB.plg_content_loadmodule.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_pagebreak.ini
+/administrator/language/en-GB/en-GB.plg_content_pagebreak.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_pagenavigation.ini
+/administrator/language/en-GB/en-GB.plg_content_pagenavigation.sys.ini
+/administrator/language/en-GB/en-GB.plg_content_vote.ini
+/administrator/language/en-GB/en-GB.plg_content_vote.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors_codemirror.ini
+/administrator/language/en-GB/en-GB.plg_editors_codemirror.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors_none.ini
+/administrator/language/en-GB/en-GB.plg_editors_none.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors_tinymce.ini
+/administrator/language/en-GB/en-GB.plg_editors_tinymce.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_article.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_article.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_image.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_image.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.sys.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.ini
+/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.sys.ini
+/administrator/language/en-GB/en-GB.plg_extension_joomla.ini
+/administrator/language/en-GB/en-GB.plg_extension_joomla.sys.ini
+/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.ini
+/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_categories.ini
+/administrator/language/en-GB/en-GB.plg_search_categories.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_contacts.ini
+/administrator/language/en-GB/en-GB.plg_search_contacts.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_content.ini
+/administrator/language/en-GB/en-GB.plg_search_content.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_newsfeeds.ini
+/administrator/language/en-GB/en-GB.plg_search_newsfeeds.sys.ini
+/administrator/language/en-GB/en-GB.plg_search_weblinks.ini
+/administrator/language/en-GB/en-GB.plg_search_weblinks.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_cache.ini
+/administrator/language/en-GB/en-GB.plg_system_cache.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_debug.ini
+/administrator/language/en-GB/en-GB.plg_system_debug.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_highlight.ini
+/administrator/language/en-GB/en-GB.plg_system_highlight.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini
+/administrator/language/en-GB/en-GB.plg_system_languagefilter.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_log.ini
+/administrator/language/en-GB/en-GB.plg_system_logout.ini
+/administrator/language/en-GB/en-GB.plg_system_logout.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_log.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_p3p.ini
+/administrator/language/en-GB/en-GB.plg_system_p3p.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_redirect.ini
+/administrator/language/en-GB/en-GB.plg_system_redirect.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_remember.ini
+/administrator/language/en-GB/en-GB.plg_system_remember.sys.ini
+/administrator/language/en-GB/en-GB.plg_system_sef.ini
+/administrator/language/en-GB/en-GB.plg_system_sef.sys.ini
+/administrator/language/en-GB/en-GB.plg_user_contactcreator.ini
+/administrator/language/en-GB/en-GB.plg_user_contactcreator.sys.ini
+/administrator/language/en-GB/en-GB.plg_user_joomla.ini
+/administrator/language/en-GB/en-GB.plg_user_joomla.sys.ini
+/administrator/language/en-GB/en-GB.plg_user_profile.ini
+/administrator/language/en-GB/en-GB.plg_user_profile.sys.ini
+/administrator/language/en-GB/en-GB.tpl_bluestork.ini
+/administrator/language/en-GB/en-GB.tpl_bluestork.sys.ini
+/administrator/language/en-GB/en-GB.tpl_hathor.ini
+/administrator/language/en-GB/en-GB.tpl_hathor.sys.ini
+/administrator/language/en-GB/en-GB.xml
+/administrator/language/en-GB/index.html
+/administrator/language/overrides/*
+/administrator/language/index.html
+/administrator/manifests/*
+/administrator/modules/mod_custom/*
+/administrator/modules/mod_feed/*
+/administrator/modules/mod_latest/*
+/administrator/modules/mod_logged/*
+/administrator/modules/mod_login/*
+/administrator/modules/mod_menu/*
+/administrator/modules/mod_multilangstatus/*
+/administrator/modules/mod_online/*
+/administrator/modules/mod_popular/*
+/administrator/modules/mod_quickicon/*
+/administrator/modules/mod_status/*
+/administrator/modules/mod_submenu/*
+/administrator/modules/mod_title/*
+/administrator/modules/mod_toolbar/*
+/administrator/modules/mod_unread/*
+/administrator/modules/mod_version/*
+/administrator/modules/mod_stats_admin/*
+/administrator/modules/index.html
+/administrator/templates/bluestork/*
+/administrator/templates/isis/*
+/administrator/templates/hathor/*
+/administrator/templates/system/*
+/administrator/templates/index.html
+/administrator/index.php
+/cache/*
+/bin/*
+/cli/*
+/components/com_banners/*
+/components/com_ajax/*
+/components/com_config/*
+/components/com_contenthistory/*
+/components/com_tags/*
+/components/com_contact/*
+/components/com_content/*
+/components/com_finder/*
+/components/com_mailto/*
+/components/com_media/*
+/components/com_newsfeeds/*
+/components/com_search/*
+/components/com_users/*
+/components/com_weblinks/*
+/components/com_wrapper/*
+/components/index.html
+/images/banners/*
+/images/headers/*
+/images/sampledata/*
+/images/joomla*
+/images/index.html
+/images/powered_by.png
+/includes/*
+/installation/*
+/language/en-GB/en-GB.com_ajax.ini
+/language/en-GB/en-GB.com_config.ini
+/language/en-GB/en-GB.com_contact.ini
+/language/en-GB/en-GB.com_finder.ini
+/language/en-GB/en-GB.com_tags.ini
+/language/en-GB/en-GB.finder_cli.ini
+/language/en-GB/en-GB.lib_fof.sys.ini
+/language/en-GB/en-GB.lib_fof.ini
+/language/en-GB/en-GB.com_content.ini
+/language/en-GB/en-GB.lib_idna_convert.sys.ini
+/language/en-GB/en-GB.com_mailto.ini
+/language/en-GB/en-GB.lib_joomla.sys.ini
+/language/en-GB/en-GB.lib_phpass.sys.ini
+/language/en-GB/en-GB.lib_phpmailer.sys.ini
+/language/en-GB/en-GB.lib_phputf8.sys.ini
+/language/en-GB/en-GB.lib_simplepie.sys.ini
+/language/en-GB/en-GB.com_media.ini
+/language/en-GB/en-GB.mod_finder.ini
+/language/en-GB/en-GB.com_messages.ini
+/language/en-GB/en-GB.mod_tags_popular.ini
+/language/en-GB/en-GB.mod_tags_popular.sys.ini
+/language/en-GB/en-GB.mod_tags_similar.ini
+/language/en-GB/en-GB.mod_tags_similar.sys.ini
+/language/en-GB/en-GB.mod_finder.sys.ini
+/language/en-GB/en-GB.tpl_beez3.ini
+/language/en-GB/en-GB.tpl_beez3.sys.ini
+/language/en-GB/en-GB.com_newsfeeds.ini
+/language/en-GB/en-GB.tpl_protostar.ini
+/language/en-GB/en-GB.tpl_protostar.sys.ini
+/language/en-GB/en-GB.com_search.ini
+/language/en-GB/en-GB.com_users.ini
+/language/en-GB/en-GB.com_weblinks.ini
+/language/en-GB/en-GB.com_wrapper.ini
+/language/en-GB/en-GB.files_joomla.sys.ini
+/language/en-GB/en-GB.ini
+/language/en-GB/en-GB.lib_joomla.ini
+/language/en-GB/en-GB.localise.php
+/language/en-GB/en-GB.mod_articles_archive.ini
+/language/en-GB/en-GB.mod_articles_archive.sys.ini
+/language/en-GB/en-GB.mod_articles_categories.ini
+/language/en-GB/en-GB.mod_articles_categories.sys.ini
+/language/en-GB/en-GB.mod_articles_category.ini
+/language/en-GB/en-GB.mod_articles_category.sys.ini
+/language/en-GB/en-GB.mod_articles_latest.ini
+/language/en-GB/en-GB.mod_articles_latest.sys.ini
+/language/en-GB/en-GB.mod_articles_news.ini
+/language/en-GB/en-GB.mod_articles_news.sys.ini
+/language/en-GB/en-GB.mod_articles_popular.ini
+/language/en-GB/en-GB.mod_articles_popular.sys.ini
+/language/en-GB/en-GB.mod_banners.ini
+/language/en-GB/en-GB.mod_banners.sys.ini
+/language/en-GB/en-GB.mod_breadcrumbs.ini
+/language/en-GB/en-GB.mod_breadcrumbs.sys.ini
+/language/en-GB/en-GB.mod_custom.ini
+/language/en-GB/en-GB.mod_custom.sys.ini
+/language/en-GB/en-GB.mod_feed.ini
+/language/en-GB/en-GB.mod_feed.sys.ini
+/language/en-GB/en-GB.mod_footer.ini
+/language/en-GB/en-GB.mod_footer.sys.ini
+/language/en-GB/en-GB.mod_languages.ini
+/language/en-GB/en-GB.mod_languages.sys.ini
+/language/en-GB/en-GB.mod_login.ini
+/language/en-GB/en-GB.mod_login.sys.ini
+/language/en-GB/en-GB.mod_menu.ini
+/language/en-GB/en-GB.mod_menu.sys.ini
+/language/en-GB/en-GB.mod_random_image.ini
+/language/en-GB/en-GB.mod_random_image.sys.ini
+/language/en-GB/en-GB.mod_related_items.ini
+/language/en-GB/en-GB.mod_related_items.sys.ini
+/language/en-GB/en-GB.mod_search.ini
+/language/en-GB/en-GB.mod_search.sys.ini
+/language/en-GB/en-GB.mod_stats.ini
+/language/en-GB/en-GB.mod_stats.sys.ini
+/language/en-GB/en-GB.mod_syndicate.ini
+/language/en-GB/en-GB.mod_syndicate.sys.ini
+/language/en-GB/en-GB.mod_users_latest.ini
+/language/en-GB/en-GB.mod_users_latest.sys.ini
+/language/en-GB/en-GB.mod_weblinks.ini
+/language/en-GB/en-GB.mod_weblinks.sys.ini
+/language/en-GB/en-GB.mod_whosonline.ini
+/language/en-GB/en-GB.mod_whosonline.sys.ini
+/language/en-GB/en-GB.mod_wrapper.ini
+/language/en-GB/en-GB.mod_wrapper.sys.ini
+/language/en-GB/en-GB.tpl_atomic.ini
+/language/en-GB/en-GB.tpl_atomic.sys.ini
+/language/en-GB/en-GB.tpl_beez_20.ini
+/language/en-GB/en-GB.tpl_beez_20.sys.ini
+/language/en-GB/en-GB.tpl_beez5.ini
+/language/en-GB/en-GB.tpl_beez5.sys.ini
+/language/en-GB/en-GB.xml
+/language/en-GB/index.html
+/language/en-GB/install.xml
+/language/overrides/*
+/language/index.html
+/layouts/joomla/*
+/layouts/libraries/*
+/layouts/plugins/*
+/layouts/index.html
+/libraries/cms.php
+/libraries/cms/*
+/libraries/fof/*
+/libraries/idna_convert/*
+/libraries/joomla/*
+/libraries/legacy/*
+/libraries/phpass/*
+/libraries/phpmailer/*
+/libraries/phputf8/*
+/libraries/simplepie/*
+/libraries/vendor/*
+/libraries/classmap.php
+/libraries/import.legacy.php
+/libraries/index.html
+/libraries/import.php
+/libraries/loader.php
+/libraries/platform.php
+/logs/*
+/media/cms/*
+/media/com_contenthistory/*
+/media/com_finder/*
+/media/com_joomlaupdate/*
+/media/com_wrapper/*
+/media/contacts/*
+/media/editors/*
+/media/jui/*
+/media/mailto/*
+/media/media/*
+/media/mod_languages/*
+/media/overrider/*
+/media/plg_quickicon_extensionupdate/*
+/media/plg_quickicon_joomlaupdate/*
+/media/plg_system_highlight/*
+/media/system/*
+/media/index.html
+/modules/mod_articles_archive/*
+/modules/mod_articles_categories/*
+/modules/mod_articles_category/*
+/modules/mod_articles_latest/*
+/modules/mod_articles_news/*
+/modules/mod_articles_popular/*
+/modules/mod_banners/*
+/modules/mod_breadcrumbs/*
+/modules/mod_custom/*
+/modules/mod_feed/*
+/modules/mod_finder/*
+/modules/mod_footer/*
+/modules/mod_languages/*
+/modules/mod_login/*
+/modules/mod_menu/*
+/modules/mod_random_image/*
+/modules/mod_related_items/*
+/modules/mod_search/*
+/modules/mod_stats/*
+/modules/mod_syndicate/*
+/modules/mod_tags_popular/*
+/modules/mod_tags_similar/*
+/modules/mod_users_latest/*
+/modules/mod_weblinks/*
+/modules/mod_whosonline/*
+/modules/mod_wrapper/*
+/modules/index.html
+/plugins/authentication/example/*
+/plugins/authentication/gmail/*
+/plugins/authentication/joomla/*
+/plugins/authentication/ldap/*
+/plugins/authentication/cookie/*
+/plugins/authentication/index.html
+/plugins/captcha/recaptcha/*
+/plugins/captcha/index.html
+/plugins/content/emailcloak/*
+/plugins/content/example/*
+/plugins/content/finder/*
+/plugins/content/geshi/*
+/plugins/content/joomla/*
+/plugins/content/loadmodule/*
+/plugins/content/pagebreak/*
+/plugins/content/pagenavigation/*
+/plugins/content/vote/*
+/plugins/content/contact/*
+/plugins/content/index.html
+/plugins/editors/codemirror/*
+/plugins/editors/none/*
+/plugins/editors/tinymce/*
+/plugins/editors/index.html
+/plugins/editors-xtd/article/*
+/plugins/editors-xtd/image/*
+/plugins/editors-xtd/pagebreak/*
+/plugins/editors-xtd/readmore/*
+/plugins/editors-xtd/index.html
+/plugins/extension/example/*
+/plugins/extension/joomla/*
+/plugins/extension/index.html
+/plugins/finder/index.html
+/plugins/finder/categories/*
+/plugins/finder/contacts/*
+/plugins/finder/content/*
+/plugins/finder/newsfeeds/*
+/plugins/finder/tags/*
+/plugins/finder/weblinks/*
+/plugins/installer/*
+/plugins/quickicon/extensionupdate/*
+/plugins/quickicon/joomlaupdate/*
+/plugins/quickicon/index.html
+/plugins/search/categories/*
+/plugins/search/contacts/*
+/plugins/search/content/*
+/plugins/search/newsfeeds/*
+/plugins/search/weblinks/*
+/plugins/search/tags/*
+/plugins/search/index.html
+/plugins/system/cache/*
+/plugins/system/debug/*
+/plugins/system/highlight/*
+/plugins/system/languagecode/*
+/plugins/system/languagefilter/*
+/plugins/system/log/*
+/plugins/system/logout/*
+/plugins/system/p3p/*
+/plugins/system/redirect/*
+/plugins/system/remember/*
+/plugins/system/sef/*
+/plugins/system/index.html
+/plugins/twofactorauth/*
+/plugins/user/contactcreator/*
+/plugins/user/example/*
+/plugins/user/joomla/*
+/plugins/user/profile/*
+/plugins/user/index.html
+/plugins/index.html
+/templates/atomic/*
+/templates/beez3/*
+/templates/beez_20/*
+/templates/beez5/*
+/templates/protostar/*
+/templates/system/*
+/templates/index.html
+/tmp/*
+/configuration.php
+/index.php
+/joomla.xml
+/*.txt
+/robots.txt.dist
diff --git a/vendor/gitignore/KiCad.gitignore b/vendor/gitignore/KiCad.gitignore
new file mode 100644
index 00000000000..606ed1c7b4d
--- /dev/null
+++ b/vendor/gitignore/KiCad.gitignore
@@ -0,0 +1,20 @@
+# For PCBs designed using KiCad: http://www.kicad-pcb.org/
+
+# Temporary files
+*.000
+*.bak
+*.bck
+*.kicad_pcb-bak
+*~
+_autosave-*
+*.tmp
+
+# Netlist files (exported from Eeschema)
+*.net
+
+# Autorouter files (exported from Pcbnew)
+.dsn
+
+# Exported BOM files
+*.xml
+*.csv
diff --git a/vendor/gitignore/Kohana.gitignore b/vendor/gitignore/Kohana.gitignore
new file mode 100644
index 00000000000..8b2ab01a800
--- /dev/null
+++ b/vendor/gitignore/Kohana.gitignore
@@ -0,0 +1,2 @@
+application/cache/*
+application/logs/*
diff --git a/vendor/gitignore/LabVIEW.gitignore b/vendor/gitignore/LabVIEW.gitignore
new file mode 100644
index 00000000000..122450865cf
--- /dev/null
+++ b/vendor/gitignore/LabVIEW.gitignore
@@ -0,0 +1,16 @@
+# Libraries
+*.lvlibp
+*.llb
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+
+# Metadata
+*.aliases
+*.lvlps
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
new file mode 100644
index 00000000000..c491fa2bc6f
--- /dev/null
+++ b/vendor/gitignore/Laravel.gitignore
@@ -0,0 +1,16 @@
+vendor/
+node_modules/
+
+# Laravel 4 specific
+bootstrap/compiled.php
+app/storage/
+
+# Laravel 5 & Lumen specific
+bootstrap/cache/
+storage/
+.env.*.php
+.env.php
+.env
+
+# Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer
+.rocketeer/
diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore
new file mode 100644
index 00000000000..47fed6c20d9
--- /dev/null
+++ b/vendor/gitignore/Leiningen.gitignore
@@ -0,0 +1,12 @@
+pom.xml
+pom.xml.asc
+*jar
+/lib/
+/classes/
+/target/
+/checkouts/
+.lein-deps-sum
+.lein-repl-history
+.lein-plugins/
+.lein-failures
+.nrepl-port
diff --git a/vendor/gitignore/LemonStand.gitignore b/vendor/gitignore/LemonStand.gitignore
new file mode 100644
index 00000000000..c7d94ad34b0
--- /dev/null
+++ b/vendor/gitignore/LemonStand.gitignore
@@ -0,0 +1,21 @@
+boot.php
+index.php
+install.php
+/config/*
+!/config/config.php
+/controllers/*
+/init/*
+/logs/*
+/phproad/*
+/temp/*
+/uploaded/*
+/installer_files/*
+/modules/backend/*
+/modules/blog/*
+/modules/cms/*
+/modules/core/*
+/modules/session/*
+/modules/shop/*
+/modules/system/*
+/modules/users/*
+# add content_*.php if you don't want erase client changes to content
diff --git a/vendor/gitignore/Lilypond.gitignore b/vendor/gitignore/Lilypond.gitignore
new file mode 100644
index 00000000000..513e6edd9c4
--- /dev/null
+++ b/vendor/gitignore/Lilypond.gitignore
@@ -0,0 +1,6 @@
+*.pdf
+*.ps
+*.midi
+*.mid
+*.log
+*~
diff --git a/vendor/gitignore/Lithium.gitignore b/vendor/gitignore/Lithium.gitignore
new file mode 100644
index 00000000000..7b22568ea89
--- /dev/null
+++ b/vendor/gitignore/Lithium.gitignore
@@ -0,0 +1,2 @@
+libraries/*
+resources/tmp/*
diff --git a/vendor/gitignore/Lua.gitignore b/vendor/gitignore/Lua.gitignore
new file mode 100644
index 00000000000..6fd0a376dec
--- /dev/null
+++ b/vendor/gitignore/Lua.gitignore
@@ -0,0 +1,41 @@
+# Compiled Lua sources
+luac.out
+
+# luarocks build files
+*.src.rock
+*.zip
+*.tar.gz
+
+# Object files
+*.o
+*.os
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+*.def
+*.exp
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
diff --git a/vendor/gitignore/Magento.gitignore b/vendor/gitignore/Magento.gitignore
new file mode 100644
index 00000000000..195c9b7a029
--- /dev/null
+++ b/vendor/gitignore/Magento.gitignore
@@ -0,0 +1,104 @@
+.htaccess.sample
+.modgit/
+.modman/
+app/code/community/Phoenix/Moneybookers/
+app/code/community/Cm/RedisSession/
+app/code/core/
+app/design/adminhtml/default/default/
+app/design/frontend/base/
+app/design/frontend/rwd/
+app/design/frontend/default/blank/
+app/design/frontend/default/default/
+app/design/frontend/default/iphone/
+app/design/frontend/default/modern/
+app/design/frontend/enterprise/default
+app/design/install/
+app/etc/modules/Enterprise_*
+app/etc/modules/Mage_*.xml
+app/etc/modules/Phoenix_Moneybookers.xml
+app/etc/modules/Cm_RedisSession.xml
+app/etc/applied.patches.list
+app/etc/config.xml
+app/etc/enterprise.xml
+app/etc/local.xml.additional
+app/etc/local.xml.template
+app/etc/local.xml
+app/.htaccess
+app/bootstrap.php
+app/locale/en_US/
+app/Mage.php
+/cron.php
+cron.sh
+dev/.htaccess
+dev/tests/functional/
+downloader/
+errors/
+favicon.ico
+/get.php
+includes/
+/index.php
+index.php.sample
+/install.php
+js/blank.html
+js/calendar/
+js/enterprise/
+js/extjs/
+js/firebug/
+js/flash/
+js/index.php
+js/jscolor/
+js/lib/
+js/mage/
+js/prototype/
+js/scriptaculous/
+js/spacer.gif
+js/tiny_mce/
+js/varien/
+lib/3Dsecure/
+lib/Apache/
+lib/flex/
+lib/googlecheckout/
+lib/.htaccess
+lib/LinLibertineFont/
+lib/Mage/
+lib/PEAR/
+lib/Pelago/
+lib/phpseclib/
+lib/Varien/
+lib/Zend/
+lib/Cm/
+lib/Credis/
+lib/Magento/
+LICENSE_AFL.txt
+LICENSE.html
+LICENSE.txt
+LICENSE_EE*
+/mage
+media/
+/api.php
+nbproject/
+pear
+pear/
+php.ini.sample
+pkginfo/
+RELEASE_NOTES.txt
+shell/.htaccess
+shell/abstract.php
+shell/compiler.php
+shell/indexer.php
+shell/log.php
+sitemap.xml
+skin/adminhtml/default/default/
+skin/adminhtml/default/enterprise
+skin/frontend/base/
+skin/frontend/rwd/
+skin/frontend/default/blank/
+skin/frontend/default/blue/
+skin/frontend/default/default/
+skin/frontend/default/french/
+skin/frontend/default/german/
+skin/frontend/default/iphone/
+skin/frontend/default/modern/
+skin/frontend/enterprise
+skin/install/
+var/
diff --git a/vendor/gitignore/Maven.gitignore b/vendor/gitignore/Maven.gitignore
new file mode 100644
index 00000000000..1cdc9f7fd45
--- /dev/null
+++ b/vendor/gitignore/Maven.gitignore
@@ -0,0 +1,9 @@
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
diff --git a/vendor/gitignore/Mercury.gitignore b/vendor/gitignore/Mercury.gitignore
new file mode 100644
index 00000000000..70ec8693971
--- /dev/null
+++ b/vendor/gitignore/Mercury.gitignore
@@ -0,0 +1,13 @@
+Mercury/
+Mercury.modules
+*.mh
+*.err
+*.init
+*.dll
+*.exe
+*.a
+*.so
+*.dylib
+*.beams
+*.d
+*.c_date
diff --git a/vendor/gitignore/MetaProgrammingSystem.gitignore b/vendor/gitignore/MetaProgrammingSystem.gitignore
new file mode 100644
index 00000000000..3e75841041c
--- /dev/null
+++ b/vendor/gitignore/MetaProgrammingSystem.gitignore
@@ -0,0 +1,16 @@
+workspace.xml
+junitvmwatcher*.properties
+build.properties
+
+# generated java classes and java source files
+# manually add any custom artifacts that can't be generated from the models
+# http://confluence.jetbrains.com/display/MPSD25/HowTo+--+MPS+and+Git
+classes_gen
+source_gen
+source_gen.caches
+
+# generated test code and test results
+test_gen
+test_gen.caches
+TEST-*.xml
+junit*.properties
diff --git a/vendor/gitignore/Nanoc.gitignore b/vendor/gitignore/Nanoc.gitignore
new file mode 100644
index 00000000000..abc21828a3e
--- /dev/null
+++ b/vendor/gitignore/Nanoc.gitignore
@@ -0,0 +1,10 @@
+# For projects using nanoc (http://nanoc.ws/)
+
+# Default location for output, needs to match output_dir's value found in config.yaml
+output/
+
+# Temporary file directory
+tmp/
+
+# Crash Log
+crash.log
diff --git a/vendor/gitignore/Nim.gitignore b/vendor/gitignore/Nim.gitignore
new file mode 100644
index 00000000000..67d9b34c6ce
--- /dev/null
+++ b/vendor/gitignore/Nim.gitignore
@@ -0,0 +1 @@
+nimcache/
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
new file mode 100644
index 00000000000..5148e527a7e
--- /dev/null
+++ b/vendor/gitignore/Node.gitignore
@@ -0,0 +1,37 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/vendor/gitignore/OCaml.gitignore b/vendor/gitignore/OCaml.gitignore
new file mode 100644
index 00000000000..f7817ae5c36
--- /dev/null
+++ b/vendor/gitignore/OCaml.gitignore
@@ -0,0 +1,20 @@
+*.annot
+*.cmo
+*.cma
+*.cmi
+*.a
+*.o
+*.cmx
+*.cmxs
+*.cmxa
+
+# ocamlbuild working directory
+_build/
+
+# ocamlbuild targets
+*.byte
+*.native
+
+# oasis generated files
+setup.data
+setup.log
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
new file mode 100644
index 00000000000..3020bc327a7
--- /dev/null
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -0,0 +1,51 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xcuserstate
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/screenshots
diff --git a/vendor/gitignore/Opa.gitignore b/vendor/gitignore/Opa.gitignore
new file mode 100644
index 00000000000..74c6219ceda
--- /dev/null
+++ b/vendor/gitignore/Opa.gitignore
@@ -0,0 +1,13 @@
+_build
+_tracks
+
+opa-debug-js
+
+*.opp
+*.opx
+*.opx.broken
+*.dump
+*.api
+*.api-txt
+*.exe
+*.log
diff --git a/vendor/gitignore/OpenCart.gitignore b/vendor/gitignore/OpenCart.gitignore
new file mode 100644
index 00000000000..28e45aa6aac
--- /dev/null
+++ b/vendor/gitignore/OpenCart.gitignore
@@ -0,0 +1,13 @@
+.htaccess
+/config.php
+admin/config.php
+
+!index.html
+
+download/
+image/data/
+image/cache/
+system/cache/
+system/logs/
+
+system/storage/
diff --git a/vendor/gitignore/OracleForms.gitignore b/vendor/gitignore/OracleForms.gitignore
new file mode 100644
index 00000000000..699a4940118
--- /dev/null
+++ b/vendor/gitignore/OracleForms.gitignore
@@ -0,0 +1,8 @@
+# Compiled Form Modules
+*.fmx
+
+# Compiled Menu Modules
+*.mmx
+
+# Compiled Pre-Linked Libraries
+*.plx
diff --git a/vendor/gitignore/Packer.gitignore b/vendor/gitignore/Packer.gitignore
new file mode 100644
index 00000000000..1b7a03efdd7
--- /dev/null
+++ b/vendor/gitignore/Packer.gitignore
@@ -0,0 +1,5 @@
+# Cache objects
+packer_cache/
+
+# For built boxes
+*.box
diff --git a/vendor/gitignore/Perl.gitignore b/vendor/gitignore/Perl.gitignore
new file mode 100644
index 00000000000..ae2ad536abb
--- /dev/null
+++ b/vendor/gitignore/Perl.gitignore
@@ -0,0 +1,20 @@
+/blib/
+/.build/
+_build/
+cover_db/
+inc/
+Build
+!Build/
+Build.bat
+.last_cover_stats
+/Makefile
+/Makefile.old
+/MANIFEST.bak
+/META.yml
+/META.json
+/MYMETA.*
+nytprof.out
+/pm_to_blib
+*.o
+*.bs
+/_eumm/
diff --git a/vendor/gitignore/Phalcon.gitignore b/vendor/gitignore/Phalcon.gitignore
new file mode 100644
index 00000000000..6ffe3aa220a
--- /dev/null
+++ b/vendor/gitignore/Phalcon.gitignore
@@ -0,0 +1,2 @@
+/cache/
+/config/development/
diff --git a/vendor/gitignore/PlayFramework.gitignore b/vendor/gitignore/PlayFramework.gitignore
new file mode 100644
index 00000000000..6d67f119175
--- /dev/null
+++ b/vendor/gitignore/PlayFramework.gitignore
@@ -0,0 +1,15 @@
+# Ignore Play! working directory #
+bin/
+/db
+.eclipse
+/lib/
+/logs/
+/modules
+/project/target
+/target
+tmp/
+test-result
+server.pid
+*.eml
+/dist/
+.cache
diff --git a/vendor/gitignore/Plone.gitignore b/vendor/gitignore/Plone.gitignore
new file mode 100644
index 00000000000..770a8681ac3
--- /dev/null
+++ b/vendor/gitignore/Plone.gitignore
@@ -0,0 +1,18 @@
+*.pyc
+*.pyo
+*.tmp*
+*.mo
+*.egg
+*.EGG
+*.egg-info
+*.EGG-INFO
+.*.cfg
+bin/
+build/
+develop-eggs/
+downloads/
+eggs/
+fake-eggs/
+parts/
+dist/
+var/
diff --git a/vendor/gitignore/Prestashop.gitignore b/vendor/gitignore/Prestashop.gitignore
new file mode 100644
index 00000000000..7c6ae1e31cc
--- /dev/null
+++ b/vendor/gitignore/Prestashop.gitignore
@@ -0,0 +1,32 @@
+# Private files
+# The following files contain your database credentials and other personal data.
+
+config/settings.*.php
+
+# Cache, temp and generated files
+# The following files are generated by PrestaShop.
+
+admin-dev/autoupgrade/
+/cache/
+!/cache/index.php
+!/cache/cachefs/index.php
+!/cache/purifier/index.php
+!/cache/push/index.php
+!/cache/sandbox/index.php
+!/cache/smarty/index.php
+!/cache/tcpdf/index.php
+config/xml/*.xml
+/log/*
+*sitemap.xml
+themes/*/cache/
+modules/*/config*.xml
+
+# Site content
+# The following folders contain product images, virtual products, CSV's, etc.
+
+admin-dev/backups/
+admin-dev/export/
+admin-dev/import/
+download/
+/img/*
+upload/
diff --git a/vendor/gitignore/Processing.gitignore b/vendor/gitignore/Processing.gitignore
new file mode 100644
index 00000000000..85f269a89f6
--- /dev/null
+++ b/vendor/gitignore/Processing.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+applet
+application.linux32
+application.linux64
+application.windows32
+application.windows64
+application.macosx
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
new file mode 100644
index 00000000000..72364f99fe4
--- /dev/null
+++ b/vendor/gitignore/Python.gitignore
@@ -0,0 +1,89 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
diff --git a/vendor/gitignore/Qooxdoo.gitignore b/vendor/gitignore/Qooxdoo.gitignore
new file mode 100644
index 00000000000..d0c64102d85
--- /dev/null
+++ b/vendor/gitignore/Qooxdoo.gitignore
@@ -0,0 +1,5 @@
+cache
+cache-downloads
+inspector
+api
+source/inspector.html
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
new file mode 100644
index 00000000000..fa24b2efee8
--- /dev/null
+++ b/vendor/gitignore/Qt.gitignore
@@ -0,0 +1,38 @@
+# C++ objects and libs
+
+*.slo
+*.lo
+*.o
+*.a
+*.la
+*.lai
+*.so
+*.dll
+*.dylib
+
+# Qt-es
+
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+moc_*.cpp
+qrc_*.cpp
+ui_*.h
+Makefile*
+*build-*
+
+# QtCreator
+
+*.autosave
+
+# QtCtreator Qml
+*.qmlproject.user
+*.qmlproject.user.*
+
+# QtCtreator CMake
+CMakeLists.txt.user
+
diff --git a/vendor/gitignore/R.gitignore b/vendor/gitignore/R.gitignore
new file mode 100644
index 00000000000..fcff087aebb
--- /dev/null
+++ b/vendor/gitignore/R.gitignore
@@ -0,0 +1,33 @@
+# History files
+.Rhistory
+.Rapp.history
+
+# Session Data files
+.RData
+
+# Example code in package build process
+*-Ex.R
+
+# Output files from R CMD build
+/*.tar.gz
+
+# Output files from R CMD check
+/*.Rcheck/
+
+# RStudio files
+.Rproj.user/
+
+# produced vignettes
+vignettes/*.html
+vignettes/*.pdf
+
+# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
+.httr-oauth
+
+# knitr and R markdown default cache directories
+/*_cache/
+/cache/
+
+# Temporary files created by R markdown
+*.utf8.md
+*.knit.md
diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md
new file mode 100644
index 00000000000..43131e815cc
--- /dev/null
+++ b/vendor/gitignore/README.md
@@ -0,0 +1,14 @@
+# .gitignore templates
+
+This directory contains language-specific .gitignore templates that are used by GitLab.
+
+These files were automatically pulled from [this repository](https://github.com/github/gitignore).
+Please submit pull requests to that repository. There is no need to edit the files in this directory.
+
+## Bulk Update
+
+To update this directory with the latest changes in the repository, run:
+
+```sh
+bundle exec rake gitlab:update_gitignore
+```
diff --git a/vendor/gitignore/ROS.gitignore b/vendor/gitignore/ROS.gitignore
new file mode 100644
index 00000000000..f8bcd117371
--- /dev/null
+++ b/vendor/gitignore/ROS.gitignore
@@ -0,0 +1,47 @@
+build/
+bin/
+lib/
+msg_gen/
+srv_gen/
+msg/*Action.msg
+msg/*ActionFeedback.msg
+msg/*ActionGoal.msg
+msg/*ActionResult.msg
+msg/*Feedback.msg
+msg/*Goal.msg
+msg/*Result.msg
+msg/_*.py
+
+# Generated by dynamic reconfigure
+*.cfgc
+/cfg/cpp/
+/cfg/*.py
+
+# Ignore generated docs
+*.dox
+*.wikidoc
+
+# eclipse stuff
+.project
+.cproject
+
+# qcreator stuff
+CMakeLists.txt.user
+
+srv/_*.py
+*.pcd
+*.pyc
+qtcreator-*
+*.user
+
+/planning/cfg
+/planning/docs
+/planning/src
+
+*~
+
+# Emacs
+.#*
+
+# Catkin custom files
+CATKIN_IGNORE
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
new file mode 100644
index 00000000000..2121e0a8038
--- /dev/null
+++ b/vendor/gitignore/Rails.gitignore
@@ -0,0 +1,38 @@
+*.rbc
+capybara-*.html
+.rspec
+/log
+/tmp
+/db/*.sqlite3
+/db/*.sqlite3-journal
+/public/system
+/coverage/
+/spec/tmp
+**.orig
+rerun.txt
+pickle-email-*.html
+
+# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
+config/initializers/secret_token.rb
+config/secrets.yml
+
+## Environment normalization:
+/.bundle
+/vendor/bundle
+
+# these should all be checked in to normalize the environment:
+# Gemfile.lock, .ruby-version, .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
+
+# if using bower-rails ignore default bower_components path bower.json files
+/vendor/assets/bower_components
+*.bowerrc
+bower.json
+
+# Ignore pow environment settings
+.powenv
+
+# Ignore Byebug command history file.
+.byebug_history
diff --git a/vendor/gitignore/RhodesRhomobile.gitignore b/vendor/gitignore/RhodesRhomobile.gitignore
new file mode 100644
index 00000000000..a211dcc3b0f
--- /dev/null
+++ b/vendor/gitignore/RhodesRhomobile.gitignore
@@ -0,0 +1,9 @@
+rholog-*
+sim-*
+bin/libs
+bin/RhoBundle
+bin/tmp
+bin/target
+bin/*.ap_
+*.o
+*.jar
diff --git a/vendor/gitignore/Ruby.gitignore b/vendor/gitignore/Ruby.gitignore
new file mode 100644
index 00000000000..5e1422c9c3f
--- /dev/null
+++ b/vendor/gitignore/Ruby.gitignore
@@ -0,0 +1,50 @@
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+
+# Used by dotenv library to load environment variables.
+# .env
+
+## Specific to RubyMotion:
+.dat*
+.repl_history
+build/
+*.bridgesupport
+build-iPhoneOS/
+build-iPhoneSimulator/
+
+## Specific to RubyMotion (use of CocoaPods):
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# vendor/Pods/
+
+## Documentation cache and generated files:
+/.yardoc/
+/_yardoc/
+/doc/
+/rdoc/
+
+## Environment normalization:
+/.bundle/
+/vendor/bundle
+/lib/bundler/man/
+
+# for a library or gem, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# Gemfile.lock
+# .ruby-version
+# .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
diff --git a/vendor/gitignore/Rust.gitignore b/vendor/gitignore/Rust.gitignore
new file mode 100644
index 00000000000..cb14a420640
--- /dev/null
+++ b/vendor/gitignore/Rust.gitignore
@@ -0,0 +1,7 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+Cargo.lock
diff --git a/vendor/gitignore/SCons.gitignore b/vendor/gitignore/SCons.gitignore
new file mode 100644
index 00000000000..39d9743a082
--- /dev/null
+++ b/vendor/gitignore/SCons.gitignore
@@ -0,0 +1,2 @@
+# for projects that use SCons for building: http://http://www.scons.org/
+.sconsign.dblite
diff --git a/vendor/gitignore/Sass.gitignore b/vendor/gitignore/Sass.gitignore
new file mode 100644
index 00000000000..486b32ce90c
--- /dev/null
+++ b/vendor/gitignore/Sass.gitignore
@@ -0,0 +1,2 @@
+.sass-cache/
+*.css.map
diff --git a/vendor/gitignore/Scala.gitignore b/vendor/gitignore/Scala.gitignore
new file mode 100644
index 00000000000..c58d83b3189
--- /dev/null
+++ b/vendor/gitignore/Scala.gitignore
@@ -0,0 +1,17 @@
+*.class
+*.log
+
+# sbt specific
+.cache
+.history
+.lib/
+dist/*
+target/
+lib_managed/
+src_managed/
+project/boot/
+project/plugins/project/
+
+# Scala-IDE specific
+.scala_dependencies
+.worksheet
diff --git a/vendor/gitignore/Scheme.gitignore b/vendor/gitignore/Scheme.gitignore
new file mode 100644
index 00000000000..cbb89d78da5
--- /dev/null
+++ b/vendor/gitignore/Scheme.gitignore
@@ -0,0 +1,7 @@
+*.ss~
+*.ss#*
+.#*.ss
+
+*.scm~
+*.scm#*
+.#*.scm
diff --git a/vendor/gitignore/Scrivener.gitignore b/vendor/gitignore/Scrivener.gitignore
new file mode 100644
index 00000000000..3b39c66ba12
--- /dev/null
+++ b/vendor/gitignore/Scrivener.gitignore
@@ -0,0 +1,7 @@
+/Files/binder.autosave
+/Files/binder.backup
+/Files/search.indexes
+/Files/user.lock
+/Files/Docs/docs.checksum
+/QuickLook/
+/Settings/ui.plist
diff --git a/vendor/gitignore/Sdcc.gitignore b/vendor/gitignore/Sdcc.gitignore
new file mode 100644
index 00000000000..07ee7d59aba
--- /dev/null
+++ b/vendor/gitignore/Sdcc.gitignore
@@ -0,0 +1,8 @@
+# SDCC stuff
+*.lnk
+*.lst
+*.map
+*.mem
+*.rel
+*.rst
+*.sym
diff --git a/vendor/gitignore/SeamGen.gitignore b/vendor/gitignore/SeamGen.gitignore
new file mode 100644
index 00000000000..a418cf376c5
--- /dev/null
+++ b/vendor/gitignore/SeamGen.gitignore
@@ -0,0 +1,26 @@
+/bootstrap/data
+/bootstrap/tmp
+/classes/
+/dist/
+/exploded-archives/
+/test-build/
+/test-output/
+/test-report/
+/target/
+temp-testng-customsuite.xml
+
+# based on http://stackoverflow.com/a/8865858/422476 I am removing inline comments
+
+#/classes/ all class files
+#/dist/ contains generated war files for deployment
+#/exploded-archives/ war content generation during deploy (or explode)
+#/test-build/ test compilation (ant target for Seam)
+#/test-output/ test results
+#/test-report/ test report generation for, e.g., Hudson
+#/target/ maven output folder
+#temp-testng-customsuite.xml generated when running test cases under Eclipse
+
+# Thanks to @VonC and @kraftan for their helpful answers on a related question
+# on StackOverflow.com:
+# http://stackoverflow.com/questions/4176687
+# /what-is-the-recommended-source-control-ignore-pattern-for-seam-projects
diff --git a/vendor/gitignore/SketchUp.gitignore b/vendor/gitignore/SketchUp.gitignore
new file mode 100644
index 00000000000..5160df3c6bf
--- /dev/null
+++ b/vendor/gitignore/SketchUp.gitignore
@@ -0,0 +1 @@
+*.skb
diff --git a/vendor/gitignore/Smalltalk.gitignore b/vendor/gitignore/Smalltalk.gitignore
new file mode 100644
index 00000000000..75272b23472
--- /dev/null
+++ b/vendor/gitignore/Smalltalk.gitignore
@@ -0,0 +1,18 @@
+# changes file
+*.changes
+
+# system image
+*.image
+
+# Pharo Smalltalk Debug log file
+PharoDebug.log
+
+# Squeak Smalltalk Debug log file
+SqueakDebug.log
+
+# Monticello package cache
+/package-cache
+
+# Metacello-github cache
+/github-cache
+github-*.zip
diff --git a/vendor/gitignore/Stella.gitignore b/vendor/gitignore/Stella.gitignore
new file mode 100644
index 00000000000..402a5438373
--- /dev/null
+++ b/vendor/gitignore/Stella.gitignore
@@ -0,0 +1,12 @@
+# Atari 2600 (Stella) support for multiple assemblers
+# - DASM
+# - CC65
+
+# Assembled binaries and object directories
+obj/
+a.out
+*.bin
+*.a26
+
+# Add in special Atari 7800-based binaries for good measure
+*.a78
diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore
new file mode 100644
index 00000000000..842c3ec518b
--- /dev/null
+++ b/vendor/gitignore/SugarCRM.gitignore
@@ -0,0 +1,25 @@
+## SugarCRM
+# Ignore custom .htaccess stuff.
+/.htaccess
+# Ignore the cache directory completely.
+# This will break the current behaviour. Which was often leading to
+# the misuse of the repository as backup replacement.
+# For development the cache directory can be safely ignored and
+# therefore it is ignored.
+/cache/
+# Ignore some files and directories from the custom directory.
+/custom/history/
+/custom/modulebuilder/
+/custom/working/
+/custom/modules/*/Ext/
+/custom/application/Ext/
+# Custom configuration should also be ignored.
+/config.php
+/config_override.php
+# The silent upgrade scripts aren't needed.
+/silentUpgrade*.php
+# Logs files can safely be ignored.
+*.log
+# Ignore the new upload directories.
+/upload/
+/upload_backup/
diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore
new file mode 100644
index 00000000000..8a29fa52af4
--- /dev/null
+++ b/vendor/gitignore/Swift.gitignore
@@ -0,0 +1,63 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xcuserstate
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
diff --git a/vendor/gitignore/Symfony.gitignore b/vendor/gitignore/Symfony.gitignore
new file mode 100644
index 00000000000..7d56f982f81
--- /dev/null
+++ b/vendor/gitignore/Symfony.gitignore
@@ -0,0 +1,48 @@
+# Cache and logs (Symfony2)
+/app/cache/*
+/app/logs/*
+!app/cache/.gitkeep
+!app/logs/.gitkeep
+
+# Email spool folder
+/app/spool/*
+
+# Cache, session files and logs (Symfony3)
+/var/cache/*
+/var/logs/*
+/var/sessions/*
+!var/cache/.gitkeep
+!var/logs/.gitkeep
+!var/sessions/.gitkeep
+
+# Parameters
+/app/config/parameters.yml
+/app/config/parameters.ini
+
+# Managed by Composer
+/app/bootstrap.php.cache
+/var/bootstrap.php.cache
+/bin/*
+!bin/console
+!bin/symfony_requirements
+/vendor/
+
+# Assets and user uploads
+/web/bundles/
+/web/uploads/
+
+# Assets managed by Bower
+/web/assets/vendor/
+
+# PHPUnit
+/app/phpunit.xml
+/phpunit.xml
+
+# Build data
+/build/
+
+# Composer PHAR
+/composer.phar
+
+# Backup entities generated with doctrine:generate:entities command
+*/Entity/*~
diff --git a/vendor/gitignore/SymphonyCMS.gitignore b/vendor/gitignore/SymphonyCMS.gitignore
new file mode 100644
index 00000000000..671c7ff9e32
--- /dev/null
+++ b/vendor/gitignore/SymphonyCMS.gitignore
@@ -0,0 +1,6 @@
+manifest/cache/
+manifest/logs/
+manifest/tmp/
+symphony/
+workspace/uploads/
+install-log.txt
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
new file mode 100644
index 00000000000..4123a577c47
--- /dev/null
+++ b/vendor/gitignore/TeX.gitignore
@@ -0,0 +1,180 @@
+## Core latex/pdflatex auxiliary files:
+*.aux
+*.lof
+*.log
+*.lot
+*.fls
+*.out
+*.toc
+*.fmt
+*.fot
+*.cb
+*.cb2
+
+## Intermediate documents:
+*.dvi
+*-converted-to.*
+# these rules might exclude image files for figures etc.
+# *.ps
+# *.eps
+# *.pdf
+
+## Bibliography auxiliary files (bibtex/biblatex/biber):
+*.bbl
+*.bcf
+*.blg
+*-blx.aux
+*-blx.bib
+*.brf
+*.run.xml
+
+## Build tool auxiliary files:
+*.fdb_latexmk
+*.synctex
+*.synctex.gz
+*.synctex.gz(busy)
+*.pdfsync
+
+## Auxiliary and intermediate files from other packages:
+# algorithms
+*.alg
+*.loa
+
+# achemso
+acs-*.bib
+
+# amsthm
+*.thm
+
+# beamer
+*.nav
+*.snm
+*.vrb
+
+# cprotect
+*.cpt
+
+# fixme
+*.lox
+
+#(r)(e)ledmac/(r)(e)ledpar
+*.end
+*.?end
+*.[1-9]
+*.[1-9][0-9]
+*.[1-9][0-9][0-9]
+*.[1-9]R
+*.[1-9][0-9]R
+*.[1-9][0-9][0-9]R
+*.eledsec[1-9]
+*.eledsec[1-9]R
+*.eledsec[1-9][0-9]
+*.eledsec[1-9][0-9]R
+*.eledsec[1-9][0-9][0-9]
+*.eledsec[1-9][0-9][0-9]R
+
+# glossaries
+*.acn
+*.acr
+*.glg
+*.glo
+*.gls
+*.glsdefs
+
+# gnuplottex
+*-gnuplottex-*
+
+# hyperref
+*.brf
+
+# knitr
+*-concordance.tex
+# TODO Comment the next line if you want to keep your tikz graphics files
+*.tikz
+*-tikzDictionary
+
+# listings
+*.lol
+
+# makeidx
+*.idx
+*.ilg
+*.ind
+*.ist
+
+# minitoc
+*.maf
+*.mlf
+*.mlt
+*.mtc
+*.mtc[0-9]
+*.mtc[1-9][0-9]
+
+# minted
+_minted*
+*.pyg
+
+# morewrites
+*.mw
+
+# mylatexformat
+*.fmt
+
+# nomencl
+*.nlo
+
+# sagetex
+*.sagetex.sage
+*.sagetex.py
+*.sagetex.scmd
+
+# sympy
+*.sout
+*.sympy
+sympy-plots-for-*.tex/
+
+# pdfcomment
+*.upa
+*.upb
+
+# pythontex
+*.pytxcode
+pythontex-files-*/
+
+# thmtools
+*.loe
+
+# TikZ & PGF
+*.dpth
+*.md5
+*.auxlock
+
+# todonotes
+*.tdo
+
+# xindy
+*.xdy
+
+# xypic precompiled matrices
+*.xyc
+
+# endfloat
+*.ttt
+*.fff
+
+# Latexian
+TSWLatexianTemp*
+
+## Editors:
+# WinEdt
+*.bak
+*.sav
+
+# Texpad
+.texpadtmp
+
+# Kile
+*.backup
+
+# KBibTeX
+*~[0-9]*
diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore
new file mode 100644
index 00000000000..7868d16d216
--- /dev/null
+++ b/vendor/gitignore/Terraform.gitignore
@@ -0,0 +1,3 @@
+# Compiled files
+*.tfstate
+*.tfstate.backup
diff --git a/vendor/gitignore/Textpattern.gitignore b/vendor/gitignore/Textpattern.gitignore
new file mode 100644
index 00000000000..3805636d622
--- /dev/null
+++ b/vendor/gitignore/Textpattern.gitignore
@@ -0,0 +1,11 @@
+.htaccess
+css.php
+rpc/
+sites/site*/admin/
+sites/site*/private/
+sites/site*/public/admin/
+sites/site*/public/setup/
+sites/site*/public/theme/
+textpattern/
+HISTORY.txt
+README.txt
diff --git a/vendor/gitignore/TurboGears2.gitignore b/vendor/gitignore/TurboGears2.gitignore
new file mode 100644
index 00000000000..122b3de221f
--- /dev/null
+++ b/vendor/gitignore/TurboGears2.gitignore
@@ -0,0 +1,20 @@
+*.py[co]
+
+# Default development database
+devdata.db
+
+# Default data directory
+data/*
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
diff --git a/vendor/gitignore/Typo3.gitignore b/vendor/gitignore/Typo3.gitignore
new file mode 100644
index 00000000000..cb024fefe99
--- /dev/null
+++ b/vendor/gitignore/Typo3.gitignore
@@ -0,0 +1,20 @@
+## TYPO3 v6.2
+# Ignore several upload and file directories.
+/fileadmin/user_upload/
+/fileadmin/_temp_/
+/fileadmin/_processed_/
+/uploads/
+# Ignore cache
+/typo3conf/temp_CACHED*
+/typo3conf/temp_fieldInfo.php
+/typo3conf/deprecation_*.log
+/typo3conf/AdditionalConfiguration.php
+# Ignore system folders, you should have them symlinked.
+# If not comment out the following entries.
+/typo3
+/typo3_src
+/typo3_src-*
+/.htaccess
+/index.php
+# Ignore temp directory.
+/typo3temp/
diff --git a/vendor/gitignore/Umbraco.gitignore b/vendor/gitignore/Umbraco.gitignore
new file mode 100644
index 00000000000..ea05e1fb2a9
--- /dev/null
+++ b/vendor/gitignore/Umbraco.gitignore
@@ -0,0 +1,19 @@
+# Note: VisualStudio gitignore rules may also be relevant
+
+# Umbraco
+# Ignore unimportant folders generated by Umbraco
+**/App_Data/Logs/
+**/App_Data/[Pp]review/
+**/App_Data/TEMP/
+**/App_Data/NuGetBackup/
+
+# Ignore Umbraco content cache file
+**/App_Data/umbraco.config
+
+# Don't ignore Umbraco packages (VisualStudio.gitignore mistakes this for a NuGet packages folder)
+# Make sure to include details from VisualStudio.gitignore BEFORE this
+!**/App_Data/[Pp]ackages/
+!**/[Uu]mbraco/[Dd]eveloper/[Pp]ackages
+
+# ImageProcessor DiskCache
+**/App_Data/cache/
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
new file mode 100644
index 00000000000..5aafcbb7f1d
--- /dev/null
+++ b/vendor/gitignore/Unity.gitignore
@@ -0,0 +1,30 @@
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/Assets/AssetStoreTools*
+
+# Autogenerated VS/MD solution and project files
+ExportedObj/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+
+
+# Unity3D generated meta files
+*.pidb.meta
+
+# Unity3D Generated File On Crash Reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.unitypackage
diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
new file mode 100644
index 00000000000..75b1186b0af
--- /dev/null
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -0,0 +1,62 @@
+# Visual Studio 2015 user specific files
+.vs/
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+*.ipa
+
+# These project files can be generated by the engine
+*.xcodeproj
+*.sln
+*.suo
+*.opensdf
+*.sdf
+*.VC.opendb
+
+# Precompiled Assets
+SourceArt/**/*.png
+SourceArt/**/*.tga
+
+# Binary Files
+Binaries/*
+
+# Builds
+Build/*
+
+# Don't ignore icon files in Build
+!Build/**/*.ico
+
+# Configuration files generated by the Editor
+Saved/*
+
+# Compiled source files for the engine to use
+Intermediate/*
+
+# Cache files for the editor to use
+DerivedDataCache/*
diff --git a/vendor/gitignore/VVVV.gitignore b/vendor/gitignore/VVVV.gitignore
new file mode 100644
index 00000000000..5df4324603e
--- /dev/null
+++ b/vendor/gitignore/VVVV.gitignore
@@ -0,0 +1,6 @@
+
+# .v4p backup files
+*~.xml
+
+# Dynamic plugins .dll
+bin/
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
new file mode 100644
index 00000000000..f1e3d20e056
--- /dev/null
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -0,0 +1,252 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
diff --git a/vendor/gitignore/Waf.gitignore b/vendor/gitignore/Waf.gitignore
new file mode 100644
index 00000000000..48e8d8f7be4
--- /dev/null
+++ b/vendor/gitignore/Waf.gitignore
@@ -0,0 +1,4 @@
+# for projects that use Waf for building: http://code.google.com/p/waf/
+.waf-*
+.waf3-*
+.lock-*
diff --git a/vendor/gitignore/WordPress.gitignore b/vendor/gitignore/WordPress.gitignore
new file mode 100644
index 00000000000..97923503c4c
--- /dev/null
+++ b/vendor/gitignore/WordPress.gitignore
@@ -0,0 +1,18 @@
+*.log
+wp-config.php
+wp-content/advanced-cache.php
+wp-content/backup-db/
+wp-content/backups/
+wp-content/blogs.dir/
+wp-content/cache/
+wp-content/upgrade/
+wp-content/uploads/
+wp-content/wp-cache-config.php
+wp-content/plugins/hello.php
+
+/.htaccess
+/license.txt
+/readme.html
+/sitemap.xml
+/sitemap.xml.gz
+
diff --git a/vendor/gitignore/Xojo.gitignore b/vendor/gitignore/Xojo.gitignore
new file mode 100644
index 00000000000..1b036dd4f2e
--- /dev/null
+++ b/vendor/gitignore/Xojo.gitignore
@@ -0,0 +1,11 @@
+# Xojo (formerly REALbasic and Real Studio)
+
+Builds*
+*.debug
+*.debug.app
+Debug*.exe
+Debug*/Debug*.exe
+Debug*/Debug*\ Libs
+*.rbuistate
+*.xojo_uistate
+*.obsolete
diff --git a/vendor/gitignore/Yeoman.gitignore b/vendor/gitignore/Yeoman.gitignore
new file mode 100644
index 00000000000..7170d72018d
--- /dev/null
+++ b/vendor/gitignore/Yeoman.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+bower_components/
+*.log
+
+build/
+dist/
diff --git a/vendor/gitignore/Yii.gitignore b/vendor/gitignore/Yii.gitignore
new file mode 100644
index 00000000000..70f087546f2
--- /dev/null
+++ b/vendor/gitignore/Yii.gitignore
@@ -0,0 +1,6 @@
+assets/*
+!assets/.gitignore
+protected/runtime/*
+!protected/runtime/.gitignore
+protected/data/*.db
+themes/classic/views/
diff --git a/vendor/gitignore/ZendFramework.gitignore b/vendor/gitignore/ZendFramework.gitignore
new file mode 100644
index 00000000000..80adb154900
--- /dev/null
+++ b/vendor/gitignore/ZendFramework.gitignore
@@ -0,0 +1,25 @@
+# Composer files
+composer.phar
+vendor/
+
+# Local configs
+config/autoload/*.local.php
+
+# Binary gettext files
+*.mo
+
+# Data
+data/logs/
+data/cache/
+data/sessions/
+data/tmp/
+temp/
+
+#Doctrine 2
+data/DoctrineORMModule/Proxy/
+data/DoctrineORMModule/cache/
+
+
+# Legacy ZF1
+demos/
+extras/documentation
diff --git a/vendor/gitignore/Zephir.gitignore b/vendor/gitignore/Zephir.gitignore
new file mode 100644
index 00000000000..839cb5d7070
--- /dev/null
+++ b/vendor/gitignore/Zephir.gitignore
@@ -0,0 +1,26 @@
+# Cache files, generates by Zephir
+.temp/
+.libs/
+
+# Object files, generates by linker
+*.lo
+*.la
+*.o
+*.loT
+
+# Files generated by configure and Zephir,
+# not required for extension compilation.
+ext/build/
+ext/modules/
+ext/Makefile*
+ext/config*
+ext/acinclude.m4
+ext/aclocal.m4
+ext/autom4te*
+ext/install-sh
+ext/ltmain.sh
+ext/missing
+ext/mkinstalldirs
+ext/run-tests.php
+ext/.deps
+ext/libtool